about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.gitignore15
-rw-r--r--__tests__/state/models/navigation.test.ts157
-rw-r--r--android/app/build.gradle85
-rw-r--r--android/app/proguard-rules.pro4
-rw-r--r--android/app/src/debug/AndroidManifest.xml8
-rw-r--r--android/app/src/debug/java/xyz/blueskyweb/app/ReactNativeFlipper.java (renamed from android/app/src/debug/java/com/app/ReactNativeFlipper.java)3
-rw-r--r--android/app/src/main/AndroidManifest.xml82
-rw-r--r--android/app/src/main/java/xyz/blueskyweb/app/MainActivity.java68
-rw-r--r--android/app/src/main/java/xyz/blueskyweb/app/MainApplication.java76
-rw-r--r--android/app/src/main/java/xyz/blueskyweb/pubsq/MainActivity.java36
-rw-r--r--android/app/src/main/java/xyz/blueskyweb/pubsq/MainApplication.java65
-rw-r--r--android/app/src/main/jni/Android.mk49
-rw-r--r--android/app/src/release/java/xyz/blueskyweb/app/ReactNativeFlipper.java (renamed from android/app/src/release/java/com/xyz/blueskyweb/app/ReactNativeFlipper.java)0
-rw-r--r--android/build.gradle35
-rw-r--r--android/gradle.properties15
-rwxr-xr-xandroid/gradlew6
-rw-r--r--android/gradlew.bat14
-rw-r--r--android/settings.gradle14
-rw-r--r--app.json29
-rw-r--r--assets/adaptive-icon.png (renamed from ios/app/Images.xcassets/AppIcon.appiconset/1024.png)bin1172966 -> 1172966 bytes
-rw-r--r--assets/cloud-splash.png (renamed from public/img/cloud-splash.png)bin712345 -> 712345 bytes
-rw-r--r--assets/default-avatar.jpg (renamed from public/img/default-avatar.jpg)bin14528 -> 14528 bytes
-rw-r--r--assets/favicon.pngbin0 -> 2604 bytes
-rw-r--r--assets/icon.pngbin0 -> 1172966 bytes
-rw-r--r--assets/tabs-explainer.jpg (renamed from public/img/tabs-explainer.jpg)bin80104 -> 80104 bytes
-rw-r--r--babel.config.js59
-rw-r--r--index.js16
-rw-r--r--index.web.js14
-rw-r--r--ios/.xcode.env.local1
-rw-r--r--ios/LaunchScreen.storyboard43
-rw-r--r--ios/Podfile108
-rw-r--r--ios/Podfile.lock704
-rw-r--r--ios/Podfile.properties.json3
-rw-r--r--ios/app.xcodeproj/project.pbxproj717
-rw-r--r--ios/app/AppDelegate.h6
-rw-r--r--ios/app/AppDelegate.mm62
-rw-r--r--ios/app/Images.xcassets/AppIcon.appiconset/100.pngbin20585 -> 0 bytes
-rw-r--r--ios/app/Images.xcassets/AppIcon.appiconset/114.pngbin25930 -> 0 bytes
-rw-r--r--ios/app/Images.xcassets/AppIcon.appiconset/120.pngbin28291 -> 0 bytes
-rw-r--r--ios/app/Images.xcassets/AppIcon.appiconset/128.pngbin31748 -> 0 bytes
-rw-r--r--ios/app/Images.xcassets/AppIcon.appiconset/144.pngbin39019 -> 0 bytes
-rw-r--r--ios/app/Images.xcassets/AppIcon.appiconset/152.pngbin42803 -> 0 bytes
-rw-r--r--ios/app/Images.xcassets/AppIcon.appiconset/16.pngbin853 -> 0 bytes
-rw-r--r--ios/app/Images.xcassets/AppIcon.appiconset/167.pngbin50264 -> 0 bytes
-rw-r--r--ios/app/Images.xcassets/AppIcon.appiconset/172.pngbin52873 -> 0 bytes
-rw-r--r--ios/app/Images.xcassets/AppIcon.appiconset/180.pngbin57354 -> 0 bytes
-rw-r--r--ios/app/Images.xcassets/AppIcon.appiconset/196.pngbin66539 -> 0 bytes
-rw-r--r--ios/app/Images.xcassets/AppIcon.appiconset/20.pngbin1231 -> 0 bytes
-rw-r--r--ios/app/Images.xcassets/AppIcon.appiconset/216.pngbin78726 -> 0 bytes
-rw-r--r--ios/app/Images.xcassets/AppIcon.appiconset/256.pngbin106196 -> 0 bytes
-rw-r--r--ios/app/Images.xcassets/AppIcon.appiconset/29.pngbin2306 -> 0 bytes
-rw-r--r--ios/app/Images.xcassets/AppIcon.appiconset/32.pngbin2765 -> 0 bytes
-rw-r--r--ios/app/Images.xcassets/AppIcon.appiconset/40.pngbin4067 -> 0 bytes
-rw-r--r--ios/app/Images.xcassets/AppIcon.appiconset/48.pngbin5650 -> 0 bytes
-rw-r--r--ios/app/Images.xcassets/AppIcon.appiconset/50.pngbin6066 -> 0 bytes
-rw-r--r--ios/app/Images.xcassets/AppIcon.appiconset/512.pngbin359718 -> 0 bytes
-rw-r--r--ios/app/Images.xcassets/AppIcon.appiconset/55.pngbin7185 -> 0 bytes
-rw-r--r--ios/app/Images.xcassets/AppIcon.appiconset/57.pngbin7636 -> 0 bytes
-rw-r--r--ios/app/Images.xcassets/AppIcon.appiconset/58.pngbin7896 -> 0 bytes
-rw-r--r--ios/app/Images.xcassets/AppIcon.appiconset/60.pngbin8368 -> 0 bytes
-rw-r--r--ios/app/Images.xcassets/AppIcon.appiconset/64.pngbin9416 -> 0 bytes
-rw-r--r--ios/app/Images.xcassets/AppIcon.appiconset/66.pngbin9932 -> 0 bytes
-rw-r--r--ios/app/Images.xcassets/AppIcon.appiconset/72.pngbin11592 -> 0 bytes
-rw-r--r--ios/app/Images.xcassets/AppIcon.appiconset/76.pngbin12757 -> 0 bytes
-rw-r--r--ios/app/Images.xcassets/AppIcon.appiconset/80.pngbin13958 -> 0 bytes
-rw-r--r--ios/app/Images.xcassets/AppIcon.appiconset/87.pngbin16124 -> 0 bytes
-rw-r--r--ios/app/Images.xcassets/AppIcon.appiconset/88.pngbin16541 -> 0 bytes
-rw-r--r--ios/app/Images.xcassets/AppIcon.appiconset/92.pngbin17827 -> 0 bytes
-rw-r--r--ios/app/Images.xcassets/AppIcon.appiconset/Contents.json346
-rw-r--r--ios/app/Images.xcassets/Contents.json6
-rw-r--r--ios/app/Images.xcassets/LaunchScreen.imageset/bsky-app-splash.pngbin712345 -> 0 bytes
-rw-r--r--ios/app/Images.xcassets/LaunchScreen.imageset/bsky-app-splash@2x.pngbin2360722 -> 0 bytes
-rw-r--r--ios/app/Images.xcassets/LaunchScreen.imageset/bsky-app-splash@3x.pngbin4827311 -> 0 bytes
-rw-r--r--ios/app/Info.plist86
-rw-r--r--ios/bluesky.xcodeproj/project.pbxproj521
-rw-r--r--ios/bluesky.xcodeproj/project.xcworkspace/contents.xcworkspacedata7
-rw-r--r--ios/bluesky.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (renamed from ios/app.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist)0
-rw-r--r--ios/bluesky.xcodeproj/xcshareddata/xcschemes/bluesky.xcscheme (renamed from ios/app.xcodeproj/xcshareddata/xcschemes/app.xcscheme)26
-rw-r--r--ios/bluesky.xcworkspace/contents.xcworkspacedata (renamed from ios/app.xcworkspace/contents.xcworkspacedata)2
-rw-r--r--ios/bluesky.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (renamed from ios/app/app.entitlements)6
-rw-r--r--ios/bluesky/AppDelegate.h7
-rw-r--r--ios/bluesky/AppDelegate.mm72
-rw-r--r--ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-20x20@1x.pngbin0 -> 966 bytes
-rw-r--r--ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-20x20@2x.pngbin0 -> 3051 bytes
-rw-r--r--ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-20x20@3x.pngbin0 -> 6132 bytes
-rw-r--r--ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-29x29@1x.pngbin0 -> 1766 bytes
-rw-r--r--ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-29x29@2x.pngbin0 -> 5795 bytes
-rw-r--r--ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-29x29@3x.pngbin0 -> 11680 bytes
-rw-r--r--ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-40x40@1x.pngbin0 -> 3051 bytes
-rw-r--r--ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-40x40@2x.pngbin0 -> 10119 bytes
-rw-r--r--ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-40x40@3x.pngbin0 -> 20332 bytes
-rw-r--r--ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-60x60@2x.pngbin0 -> 20332 bytes
-rw-r--r--ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-60x60@3x.pngbin0 -> 40513 bytes
-rw-r--r--ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-76x76@1x.pngbin0 -> 9213 bytes
-rw-r--r--ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-76x76@2x.pngbin0 -> 30371 bytes
-rw-r--r--ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-83.5x83.5@2x.pngbin0 -> 35592 bytes
-rw-r--r--ios/bluesky/Images.xcassets/AppIcon.appiconset/Contents.json122
-rw-r--r--ios/bluesky/Images.xcassets/AppIcon.appiconset/ItunesArtwork@2x.pngbin0 -> 750143 bytes
-rw-r--r--ios/bluesky/Images.xcassets/Contents.json6
-rw-r--r--ios/bluesky/Images.xcassets/SplashScreen.imageset/Contents.json21
-rw-r--r--ios/bluesky/Images.xcassets/SplashScreen.imageset/image.pngbin0 -> 549567 bytes
-rw-r--r--ios/bluesky/Images.xcassets/SplashScreenBackground.imageset/Contents.json (renamed from ios/app/Images.xcassets/LaunchScreen.imageset/Contents.json)6
-rw-r--r--ios/bluesky/Images.xcassets/SplashScreenBackground.imageset/icon 1.pngbin0 -> 1172966 bytes
-rw-r--r--ios/bluesky/Images.xcassets/SplashScreenBackground.imageset/icon 2.pngbin0 -> 1172966 bytes
-rw-r--r--ios/bluesky/Images.xcassets/SplashScreenBackground.imageset/icon.pngbin0 -> 1172966 bytes
-rw-r--r--ios/bluesky/Info.plist100
-rw-r--r--ios/bluesky/SplashScreen.storyboard52
-rw-r--r--ios/bluesky/Supporting/Expo.plist16
-rw-r--r--ios/bluesky/bluesky.entitlements8
-rw-r--r--ios/bluesky/main.m (renamed from ios/app/main.m)3
-rw-r--r--ios/bluesky/noop-file.swift4
-rw-r--r--jest/jestSetup.js14
-rw-r--r--metro.config.js20
-rw-r--r--package.json66
-rw-r--r--public/index.html24
-rw-r--r--src/App.native.tsx33
-rw-r--r--src/App.web.tsx24
-rw-r--r--src/Navigation.tsx287
-rw-r--r--src/app.json4
-rw-r--r--src/index.js12
-rw-r--r--src/lib/analytics.tsx2
-rw-r--r--src/lib/api/index.ts4
-rw-r--r--src/lib/assets.native.ts6
-rw-r--r--src/lib/build-flags.ts1
-rw-r--r--src/lib/constants.ts2
-rw-r--r--src/lib/hooks/useColorSchemeStyle.ts4
-rw-r--r--src/lib/hooks/usePermissions.ts50
-rw-r--r--src/lib/icons.tsx83
-rw-r--r--src/lib/link-meta/bsky.ts153
-rw-r--r--src/lib/media/manip.web.ts41
-rw-r--r--src/lib/media/picker.web.tsx5
-rw-r--r--src/lib/media/util.ts7
-rw-r--r--src/lib/notifee.ts4
-rw-r--r--src/lib/permissions.ts61
-rw-r--r--src/lib/permissions.web.ts22
-rw-r--r--src/lib/routes/helpers.ts77
-rw-r--r--src/lib/routes/router.ts55
-rw-r--r--src/lib/routes/types.ts61
-rw-r--r--src/lib/styles.ts6
-rw-r--r--src/routes.ts16
-rw-r--r--src/state/models/navigation.ts434
-rw-r--r--src/state/models/root-store.ts12
-rw-r--r--src/state/models/shell-ui.ts11
-rw-r--r--src/state/models/user-local-photos.ts24
-rw-r--r--src/view/com/composer/Composer.tsx (renamed from src/view/com/composer/ComposePost.tsx)378
-rw-r--r--src/view/com/composer/ExternalEmbed.tsx1
-rw-r--r--src/view/com/composer/Prompt.tsx26
-rw-r--r--src/view/com/composer/autocomplete/Autocomplete.tsx77
-rw-r--r--src/view/com/composer/autocomplete/Autocomplete.web.tsx59
-rw-r--r--src/view/com/composer/photos/OpenCameraBtn.tsx84
-rw-r--r--src/view/com/composer/photos/PhotoCarouselPicker.tsx187
-rw-r--r--src/view/com/composer/photos/PhotoCarouselPicker.web.tsx10
-rw-r--r--src/view/com/composer/photos/SelectPhotoBtn.tsx94
-rw-r--r--src/view/com/composer/photos/SelectedPhotos.tsx (renamed from src/view/com/composer/SelectedPhoto.tsx)2
-rw-r--r--src/view/com/composer/text-input/TextInput.tsx252
-rw-r--r--src/view/com/composer/text-input/TextInput.web.tsx169
-rw-r--r--src/view/com/composer/text-input/mobile/Autocomplete.tsx75
-rw-r--r--src/view/com/composer/text-input/web/Autocomplete.tsx157
-rw-r--r--src/view/com/composer/useExternalLinkFetch.ts90
-rw-r--r--src/view/com/login/CreateAccount.tsx3
-rw-r--r--src/view/com/login/Signin.tsx22
-rw-r--r--src/view/com/modals/ChangeHandle.tsx5
-rw-r--r--src/view/com/modals/DeleteAccount.tsx7
-rw-r--r--src/view/com/modals/EditProfile.tsx5
-rw-r--r--src/view/com/modals/ServerInput.tsx3
-rw-r--r--src/view/com/modals/crop-image/CropImage.web.tsx3
-rw-r--r--src/view/com/notifications/FeedItem.tsx40
-rw-r--r--src/view/com/post-thread/PostThread.tsx56
-rw-r--r--src/view/com/post-thread/PostThreadItem.tsx19
-rw-r--r--src/view/com/post/Post.tsx10
-rw-r--r--src/view/com/posts/Feed.tsx14
-rw-r--r--src/view/com/posts/FeedItem.tsx24
-rw-r--r--src/view/com/profile/ProfileCard.tsx10
-rw-r--r--src/view/com/profile/ProfileHeader.tsx105
-rw-r--r--src/view/com/util/ErrorBoundary.tsx13
-rw-r--r--src/view/com/util/Link.tsx169
-rw-r--r--src/view/com/util/LoadLatestBtn.web.tsx13
-rw-r--r--src/view/com/util/PostEmbeds/QuoteEmbed.tsx1
-rw-r--r--src/view/com/util/PostMeta.tsx95
-rw-r--r--src/view/com/util/PostMuted.tsx2
-rw-r--r--src/view/com/util/UserAvatar.tsx78
-rw-r--r--src/view/com/util/UserBanner.tsx28
-rw-r--r--src/view/com/util/UserInfoText.tsx26
-rw-r--r--src/view/com/util/ViewHeader.tsx92
-rw-r--r--src/view/com/util/Views.web.tsx7
-rw-r--r--src/view/com/util/forms/DropdownButton.tsx10
-rw-r--r--src/view/com/util/forms/RadioButton.tsx10
-rw-r--r--src/view/com/util/forms/ToggleButton.tsx12
-rw-r--r--src/view/routes.ts91
-rw-r--r--src/view/screens/Contacts.tsx88
-rw-r--r--src/view/screens/Debug.tsx6
-rw-r--r--src/view/screens/Home.tsx68
-rw-r--r--src/view/screens/Log.tsx22
-rw-r--r--src/view/screens/NotFound.tsx48
-rw-r--r--src/view/screens/Notifications.tsx40
-rw-r--r--src/view/screens/PostDownvotedBy.tsx27
-rw-r--r--src/view/screens/PostRepostedBy.tsx19
-rw-r--r--src/view/screens/PostThread.tsx78
-rw-r--r--src/view/screens/PostUpvotedBy.tsx20
-rw-r--r--src/view/screens/Profile.tsx57
-rw-r--r--src/view/screens/ProfileFollowers.tsx19
-rw-r--r--src/view/screens/ProfileFollows.tsx19
-rw-r--r--src/view/screens/Search.tsx53
-rw-r--r--src/view/screens/Search.web.tsx28
-rw-r--r--src/view/screens/Settings.tsx54
-rw-r--r--src/view/shell/BottomBar.tsx (renamed from src/view/shell/mobile/BottomBar.tsx)90
-rw-r--r--src/view/shell/Composer.tsx (renamed from src/view/shell/mobile/Composer.tsx)5
-rw-r--r--src/view/shell/Composer.web.tsx (renamed from src/view/shell/web/Composer.tsx)11
-rw-r--r--src/view/shell/Drawer.tsx386
-rw-r--r--src/view/shell/desktop/LeftNav.tsx254
-rw-r--r--src/view/shell/desktop/RightNav.tsx46
-rw-r--r--src/view/shell/desktop/Search.tsx (renamed from src/view/shell/web/DesktopSearch.tsx)26
-rw-r--r--src/view/shell/index.tsx139
-rw-r--r--src/view/shell/index.web.tsx113
-rw-r--r--src/view/shell/mobile/Menu.tsx354
-rw-r--r--src/view/shell/mobile/index.tsx335
-rw-r--r--src/view/shell/web/DesktopHeader.tsx222
-rw-r--r--src/view/shell/web/index.tsx150
-rw-r--r--web/index.html175
-rw-r--r--web/webpack.config.js121
-rw-r--r--webpack.config.js26
-rw-r--r--yarn.lock3768
222 files changed, 8702 insertions, 6335 deletions
diff --git a/.gitignore b/.gitignore
index 7453726d3..1be65ccb1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -63,4 +63,17 @@ buck-out/
 # Testing
 coverage/
 junit.xml
-artifacts
\ No newline at end of file
+artifacts
+
+.expo/
+dist/
+*.jks
+*.p8
+*.p12
+*.key
+*.mobileprovision
+*.orig.*
+web-build/
+
+# Temporary files created by Metro to check the health of the file watcher
+.metro-health-check*
\ No newline at end of file
diff --git a/__tests__/state/models/navigation.test.ts b/__tests__/state/models/navigation.test.ts
deleted file mode 100644
index 487fc18a2..000000000
--- a/__tests__/state/models/navigation.test.ts
+++ /dev/null
@@ -1,157 +0,0 @@
-import {RootStoreModel} from './../../../src/state/models/root-store'
-import {NavigationModel} from './../../../src/state/models/navigation'
-import * as flags from '../../../src/lib/build-flags'
-import AtpAgent from '@atproto/api'
-import {DEFAULT_SERVICE} from '../../../src/state'
-
-describe('NavigationModel', () => {
-  let model: NavigationModel
-  let rootStore: RootStoreModel
-
-  beforeEach(() => {
-    rootStore = new RootStoreModel(new AtpAgent({service: DEFAULT_SERVICE}))
-    model = new NavigationModel(rootStore)
-    model.setTitle('0-0', 'title')
-  })
-
-  afterAll(() => {
-    jest.clearAllMocks()
-  })
-
-  it('should clear() to the correct base state', async () => {
-    await model.clear()
-    expect(model.tabCount).toBe(3)
-    expect(model.tab).toEqual({
-      fixedTabPurpose: 0,
-      history: [
-        {
-          id: expect.anything(),
-          ts: expect.anything(),
-          url: '/',
-        },
-      ],
-      id: expect.anything(),
-      index: 0,
-      isNewTab: false,
-    })
-  })
-
-  it('should call the navigate method', async () => {
-    const navigateSpy = jest.spyOn(model.tab, 'navigate')
-    await model.navigate('testurl', 'teststring')
-    expect(navigateSpy).toHaveBeenCalledWith('testurl', 'teststring')
-  })
-
-  it('should call the refresh method', async () => {
-    const refreshSpy = jest.spyOn(model.tab, 'refresh')
-    await model.refresh()
-    expect(refreshSpy).toHaveBeenCalled()
-  })
-
-  it('should call the isCurrentScreen method', () => {
-    expect(model.isCurrentScreen('11', 0)).toEqual(false)
-  })
-
-  it('should call the tab getter', () => {
-    expect(model.tab).toEqual({
-      fixedTabPurpose: 0,
-      history: [
-        {
-          id: expect.anything(),
-          ts: expect.anything(),
-          url: '/',
-        },
-      ],
-      id: expect.anything(),
-      index: 0,
-      isNewTab: false,
-    })
-  })
-
-  it('should call the tabCount getter', () => {
-    expect(model.tabCount).toBe(3)
-  })
-
-  describe('tabs not enabled', () => {
-    jest.mock('../../../src/lib/build-flags', () => ({
-      TABS_ENABLED: false,
-    }))
-
-    afterAll(() => {
-      jest.clearAllMocks()
-    })
-
-    it('should not create new tabs', () => {
-      // @ts-expect-error
-      flags.TABS_ENABLED = false
-      model.newTab('testurl')
-      expect(model.tab.isNewTab).toBe(false)
-      expect(model.tabIndex).toBe(0)
-    })
-
-    it('should not change the active tab', () => {
-      // @ts-expect-error
-      flags.TABS_ENABLED = false
-      model.setActiveTab(3)
-      expect(model.tabIndex).toBe(0)
-    })
-
-    it('should note close tabs', () => {
-      // @ts-expect-error
-      flags.TABS_ENABLED = false
-      model.closeTab(0)
-      expect(model.tabCount).toBe(3)
-    })
-  })
-
-  // TODO restore when tabs get re-enabled
-  // describe('tabs enabled', () => {
-  //   jest.mock('../../../src/build-flags', () => ({
-  //     TABS_ENABLED: true,
-  //   }))
-
-  //   afterAll(() => {
-  //     jest.clearAllMocks()
-  //   })
-
-  //   it('should create new tabs', () => {
-  //     // @ts-expect-error
-  //     flags.TABS_ENABLED = true
-
-  //     model.newTab('testurl', 'title')
-  //     expect(model.tab.isNewTab).toBe(true)
-  //     expect(model.tabIndex).toBe(2)
-  //   })
-
-  //   it('should change the current tab', () => {
-  //     // @ts-expect-error
-  //     flags.TABS_ENABLED = true
-
-  //     model.setActiveTab(0)
-  //     expect(model.tabIndex).toBe(0)
-  //   })
-
-  //   it('should close tabs', () => {
-  //     // @ts-expect-error
-  //     flags.TABS_ENABLED = true
-
-  //     model.closeTab(0)
-  //     expect(model.tabs).toEqual([
-  //       {
-  //         fixedTabPurpose: 1,
-  //         history: [
-  //           {
-  //             id: expect.anything(),
-  //             ts: expect.anything(),
-  //             url: '/notifications',
-  //           },
-  //         ],
-  //         id: expect.anything(),
-  //         index: 0,
-  //         isNewTab: false,
-  //       },
-  //     ])
-  //     expect(model.tabIndex).toBe(0)
-  //   })
-  // })
-})
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 2c96dc30b..cbf0b1e75 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -2,14 +2,27 @@ apply plugin: "com.android.application"
 apply plugin: "com.facebook.react"
 
 import com.android.build.OutputFile
-import org.apache.tools.ant.taskdefs.condition.Os
+
+def projectRoot = rootDir.getAbsoluteFile().getParentFile().getAbsolutePath()
+def expoDebuggableVariants = ['debug']
+// Override `debuggableVariants` for expo-updates debugging
+if (System.getenv('EX_UPDATES_NATIVE_DEBUG') == "1") {
+  react {
+    expoDebuggableVariants = []
+  }
+}
+
 
 /**
  * This is the configuration block to customize your React Native Android app.
  * By default you don't need to apply any configuration, just uncomment the lines you need.
  */
-
 react {
+    entryFile = file(["node", "-e", "require('expo/scripts/resolveAppEntry')", projectRoot, "android", "absolute"].execute(null, rootDir).text.trim())
+    reactNativeDir = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile()
+    hermesCommand = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/sdks/hermesc/%OS-BIN%/hermesc"
+    debuggableVariants = expoDebuggableVariants
+
     /* Folders */
     //   The root of your project, i.e. where "package.json" lives. Default is '..'
     // root = file("../")
@@ -19,11 +32,13 @@ react {
     // codegenDir = file("../node_modules/react-native-codegen")
     //   The cli.js file which is the React Native CLI entrypoint. Default is ../node_modules/react-native/cli.js
     // cliFile = file("../node_modules/react-native/cli.js")
+
     /* Variants */
     //   The list of variants to that are debuggable. For those we're going to
     //   skip the bundling of the JS bundle and the assets. By default is just 'debug'.
     //   If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants.
     // debuggableVariants = ["liteDebug", "prodDebug"]
+
     /* Bundling */
     //   A list containing the node command and its flags. Default is just 'node'.
     // nodeExecutableAndArgs = ["node"]
@@ -43,6 +58,7 @@ react {
     //   A list of extra flags to pass to the 'bundle' commands.
     //   See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle
     // extraPackagerArgs = []
+
     /* Hermes Commands */
     //   The hermes compiler command to run. By default it is 'hermesc'
     // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc"
@@ -51,6 +67,11 @@ react {
     // hermesFlags = ["-O", "-output-source-map"]
 }
 
+// Override `hermesEnabled` by `expo.jsEngine`
+ext {
+  hermesEnabled = (findProperty('expo.jsEngine') ?: "hermes") == "hermes"
+}
+
 /**
  * Set this to true to create four separate APKs instead of one,
  * one for each native architecture. This is useful if you don't
@@ -62,7 +83,7 @@ def enableSeparateBuildPerCPUArchitecture = false
 /**
  * Set this to true to Run Proguard on Release builds to minify the Java bytecode.
  */
-def enableProguardInReleaseBuilds = false
+def enableProguardInReleaseBuilds = (findProperty('android.enableProguardInReleaseBuilds') ?: false).toBoolean()
 
 /**
  * The preferred build flavor of JavaScriptCore (JSC)
@@ -92,13 +113,13 @@ android {
 
     compileSdkVersion rootProject.ext.compileSdkVersion
 
-    namespace "xyz.blueskyweb.app"
+    namespace 'xyz.blueskyweb.app'
     defaultConfig {
-        applicationId "xyz.blueskyweb.app"
+        applicationId 'xyz.blueskyweb.app'
         minSdkVersion rootProject.ext.minSdkVersion
         targetSdkVersion rootProject.ext.targetSdkVersion
         versionCode 1
-        versionName "1.0"
+        versionName "1.0.0"
     }
 
     splits {
@@ -147,20 +168,63 @@ android {
     }
 }
 
+// Apply static values from `gradle.properties` to the `android.packagingOptions`
+// Accepts values in comma delimited lists, example:
+// android.packagingOptions.pickFirsts=/LICENSE,**/picasa.ini
+["pickFirsts", "excludes", "merges", "doNotStrip"].each { prop ->
+    // Split option: 'foo,bar' -> ['foo', 'bar']
+    def options = (findProperty("android.packagingOptions.$prop") ?: "").split(",");
+    // Trim all elements in place.
+    for (i in 0..<options.size()) options[i] = options[i].trim();
+    // `[] - ""` is essentially `[""].filter(Boolean)` removing all empty strings.
+    options -= ""
+
+    if (options.length > 0) {
+        println "android.packagingOptions.$prop += $options ($options.length)"
+        // Ex: android.packagingOptions.pickFirsts += '**/SCCS/**'
+        options.each {
+            android.packagingOptions[prop] += it
+        }
+    }
+}
+
 dependencies {
     // The version of react-native is set by the React Native Gradle Plugin
     implementation("com.facebook.react:react-android")
 
+    def isGifEnabled = (findProperty('expo.gif.enabled') ?: "") == "true";
+    def isWebpEnabled = (findProperty('expo.webp.enabled') ?: "") == "true";
+    def isWebpAnimatedEnabled = (findProperty('expo.webp.animated') ?: "") == "true";
+    def frescoVersion = rootProject.ext.frescoVersion
+
+    // If your app supports Android versions before Ice Cream Sandwich (API level 14)
+    if (isGifEnabled || isWebpEnabled) {
+        implementation("com.facebook.fresco:fresco:${frescoVersion}")
+        implementation("com.facebook.fresco:imagepipeline-okhttp3:${frescoVersion}")
+    }
+
+    if (isGifEnabled) {
+        // For animated gif support
+        implementation("com.facebook.fresco:animated-gif:${frescoVersion}")
+    }
+
+    if (isWebpEnabled) {
+        // For webp support
+        implementation("com.facebook.fresco:webpsupport:${frescoVersion}")
+        if (isWebpAnimatedEnabled) {
+            // Animated webp support
+            implementation("com.facebook.fresco:animated-webp:${frescoVersion}")
+        }
+    }
+
     implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.0.0")
-    
-    implementation project(':rn-fetch-blob')                                                                      
 
     debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}")
     debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
         exclude group:'com.squareup.okhttp3', module:'okhttp'
     }
-
     debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}")
+
     if (hermesEnabled.toBoolean()) {
         implementation("com.facebook.react:hermes-android")
     } else {
@@ -168,4 +232,5 @@ dependencies {
     }
 }
 
-apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
+apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json')"].execute(null, rootDir).text.trim(), "../native_modules.gradle");
+applyNativeModulesAppBuildGradle(project)
diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro
index 11b025724..4f2907661 100644
--- a/android/app/proguard-rules.pro
+++ b/android/app/proguard-rules.pro
@@ -8,3 +8,7 @@
 #   http://developer.android.com/guide/developing/tools/proguard.html
 
 # Add any project specific keep options here:
+
+# react-native-reanimated
+-keep class com.swmansion.reanimated.** { *; }
+-keep class com.facebook.react.turbomodule.** { *; }
\ No newline at end of file
diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml
index 4b185bc15..99e38fc5f 100644
--- a/android/app/src/debug/AndroidManifest.xml
+++ b/android/app/src/debug/AndroidManifest.xml
@@ -1,13 +1,7 @@
-<?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools">
 
     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
 
-    <application
-        android:usesCleartextTraffic="true"
-        tools:targetApi="28"
-        tools:ignore="GoogleAppIndexingWarning">
-        <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" android:exported="false" />
-    </application>
+    <application android:usesCleartextTraffic="true" tools:targetApi="28" tools:ignore="GoogleAppIndexingWarning" />
 </manifest>
diff --git a/android/app/src/debug/java/com/app/ReactNativeFlipper.java b/android/app/src/debug/java/xyz/blueskyweb/app/ReactNativeFlipper.java
index 2402f11b0..82fa1c1d4 100644
--- a/android/app/src/debug/java/com/app/ReactNativeFlipper.java
+++ b/android/app/src/debug/java/xyz/blueskyweb/app/ReactNativeFlipper.java
@@ -4,7 +4,7 @@
  * <p>This source code is licensed under the MIT license found in the LICENSE file in the root
  * directory of this source tree.
  */
-package com.app;
+package xyz.blueskyweb.app;
 
 import android.content.Context;
 import com.facebook.flipper.android.AndroidFlipperClient;
@@ -17,7 +17,6 @@ import com.facebook.flipper.plugins.inspector.DescriptorMapping;
 import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
 import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;
 import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;
-import com.facebook.flipper.plugins.react.ReactFlipperPlugin;
 import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;
 import com.facebook.react.ReactInstanceEventListener;
 import com.facebook.react.ReactInstanceManager;
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 1bee09d51..2582a5338 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -1,47 +1,35 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-  package="xyz.blueskyweb.app">
-
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.CAMERA"/>
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
-
-    <application
-      android:name=".MainApplication"
-      android:label="@string/app_name"
-      android:icon="@mipmap/ic_launcher"
-      android:roundIcon="@mipmap/ic_launcher_round"
-      android:allowBackup="false"
-      android:theme="@style/AppTheme">
-      <activity
-        android:name=".MainActivity"
-        android:label="@string/app_name"
-        android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
-        android:launchMode="singleTask"
-        android:windowSoftInputMode="adjustPan"
-        android:exported="true">
-        <intent-filter>
-            <action android:name="android.intent.action.MAIN" />
-            <category android:name="android.intent.category.LAUNCHER" />
-        </intent-filter>
-        <!-- deep linking via web links -->
-        <intent-filter android:autoVerify="true">
-            <action android:name="android.intent.action.VIEW" />
-            <category android:name="android.intent.category.DEFAULT" />
-            <category android:name="android.intent.category.BROWSABLE" />
-            <!-- debug host: https://bsky.pfrazee.com -->
-            <data android:scheme="https" />
-            <data android:host="bsky.pfrazee.com" />
-        </intent-filter>
-        <!-- deep linking via custom links -->
-        <intent-filter>
-            <action android:name="android.intent.action.VIEW" />
-            <category android:name="android.intent.category.DEFAULT" />
-            <category android:name="android.intent.category.BROWSABLE" />
-            <!-- debug scheme: bsky://app -->
-            <data android:scheme="bsky" />
-            <data android:host="app" />
-        </intent-filter>
-      </activity>
-    </application>
-</manifest>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="xyz.blueskyweb.app">
+  <uses-permission android:name="android.permission.INTERNET"/>
+  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+  <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
+  <uses-permission android:name="android.permission.VIBRATE"/>
+  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+  <queries>
+    <intent>
+      <action android:name="android.intent.action.VIEW"/>
+      <category android:name="android.intent.category.BROWSABLE"/>
+      <data android:scheme="https"/>
+    </intent>
+  </queries>
+  <application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="true" android:theme="@style/AppTheme" android:usesCleartextTraffic="true">
+    <meta-data android:name="expo.modules.updates.ENABLED" android:value="true"/>
+    <meta-data android:name="expo.modules.updates.EXPO_SDK_VERSION" android:value="48.0.0"/>
+    <meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ALWAYS"/>
+    <meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0"/>
+    <meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="https://exp.host/@arrygoo/bluesky"/> <!-- TODO -->
+    <activity android:name=".MainActivity" android:label="@string/app_name" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.App.SplashScreen" android:exported="true" android:screenOrientation="portrait">
+      <intent-filter>
+        <action android:name="android.intent.action.MAIN"/>
+        <category android:name="android.intent.category.LAUNCHER"/>
+      </intent-filter>
+      <intent-filter>
+        <action android:name="android.intent.action.VIEW"/>
+        <category android:name="android.intent.category.DEFAULT"/>
+        <category android:name="android.intent.category.BROWSABLE"/>
+        <data android:scheme="xyz.blueskyweb.app"/>
+        <data android:scheme="exp+bluesky"/>
+      </intent-filter>
+    </activity>
+    <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" android:exported="false"/>
+  </application>
+</manifest>
\ No newline at end of file
diff --git a/android/app/src/main/java/xyz/blueskyweb/app/MainActivity.java b/android/app/src/main/java/xyz/blueskyweb/app/MainActivity.java
new file mode 100644
index 000000000..0833f982d
--- /dev/null
+++ b/android/app/src/main/java/xyz/blueskyweb/app/MainActivity.java
@@ -0,0 +1,68 @@
+package xyz.blueskyweb.app;
+
+import android.os.Build;
+import android.os.Bundle;
+
+import com.facebook.react.ReactActivity;
+import com.facebook.react.ReactActivityDelegate;
+import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
+import com.facebook.react.defaults.DefaultReactActivityDelegate;
+
+import expo.modules.ReactActivityDelegateWrapper;
+
+public class MainActivity extends ReactActivity {
+  @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    // Set the theme to AppTheme BEFORE onCreate to support 
+    // coloring the background, status bar, and navigation bar.
+    // This is required for expo-splash-screen.
+    setTheme(R.style.AppTheme);
+    super.onCreate(null);
+  }
+
+  /**
+   * Returns the name of the main component registered from JavaScript.
+   * This is used to schedule rendering of the component.
+   */
+  @Override
+  protected String getMainComponentName() {
+    return "main";
+  }
+
+  /**
+   * Returns the instance of the {@link ReactActivityDelegate}. Here we use a util class {@link
+   * DefaultReactActivityDelegate} which allows you to easily enable Fabric and Concurrent React
+   * (aka React 18) with two boolean flags.
+   */
+  @Override
+  protected ReactActivityDelegate createReactActivityDelegate() {
+    return new ReactActivityDelegateWrapper(this, BuildConfig.IS_NEW_ARCHITECTURE_ENABLED, new DefaultReactActivityDelegate(
+        this,
+        getMainComponentName(),
+        // If you opted-in for the New Architecture, we enable the Fabric Renderer.
+        DefaultNewArchitectureEntryPoint.getFabricEnabled(), // fabricEnabled
+        // If you opted-in for the New Architecture, we enable Concurrent React (i.e. React 18).
+        DefaultNewArchitectureEntryPoint.getConcurrentReactEnabled() // concurrentRootEnabled
+        ));
+  }
+
+  /**
+   * Align the back button behavior with Android S
+   * where moving root activities to background instead of finishing activities.
+   * @see <a href="https://developer.android.com/reference/android/app/Activity#onBackPressed()">onBackPressed</a>
+   */
+  @Override
+  public void invokeDefaultOnBackPressed() {
+    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
+      if (!moveTaskToBack(false)) {
+        // For non-root activities, use the default implementation to finish them.
+        super.invokeDefaultOnBackPressed();
+      }
+      return;
+    }
+
+    // Use the default back button implementation on Android S
+    // because it's doing more than {@link Activity#moveTaskToBack} in fact.
+    super.invokeDefaultOnBackPressed();
+  }
+}
diff --git a/android/app/src/main/java/xyz/blueskyweb/app/MainApplication.java b/android/app/src/main/java/xyz/blueskyweb/app/MainApplication.java
new file mode 100644
index 000000000..38de2503d
--- /dev/null
+++ b/android/app/src/main/java/xyz/blueskyweb/app/MainApplication.java
@@ -0,0 +1,76 @@
+package xyz.blueskyweb.app;
+
+import android.app.Application;
+import android.content.res.Configuration;
+import androidx.annotation.NonNull;
+
+import com.facebook.react.PackageList;
+import com.facebook.react.ReactApplication;
+import com.facebook.react.ReactNativeHost;
+import com.facebook.react.ReactPackage;
+import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
+import com.facebook.react.defaults.DefaultReactNativeHost;
+import com.facebook.soloader.SoLoader;
+
+import expo.modules.ApplicationLifecycleDispatcher;
+import expo.modules.ReactNativeHostWrapper;
+
+import java.util.List;
+
+public class MainApplication extends Application implements ReactApplication {
+
+  private final ReactNativeHost mReactNativeHost =
+    new ReactNativeHostWrapper(this, new DefaultReactNativeHost(this) {
+      @Override
+      public boolean getUseDeveloperSupport() {
+        return BuildConfig.DEBUG;
+      }
+
+      @Override
+      protected List<ReactPackage> getPackages() {
+        @SuppressWarnings("UnnecessaryLocalVariable")
+        List<ReactPackage> packages = new PackageList(this).getPackages();
+        // Packages that cannot be autolinked yet can be added manually here, for example:
+        // packages.add(new MyReactNativePackage());
+        return packages;
+      }
+
+      @Override
+      protected String getJSMainModuleName() {
+        return "index";
+      }
+
+      @Override
+      protected boolean isNewArchEnabled() {
+        return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
+      }
+
+      @Override
+      protected Boolean isHermesEnabled() {
+        return BuildConfig.IS_HERMES_ENABLED;
+      }
+  });
+
+  @Override
+  public ReactNativeHost getReactNativeHost() {
+    return mReactNativeHost;
+  }
+
+  @Override
+  public void onCreate() {
+    super.onCreate();
+    SoLoader.init(this, /* native exopackage */ false);
+    if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
+      // If you opted-in for the New Architecture, we load the native entry point for this app.
+      DefaultNewArchitectureEntryPoint.load();
+    }
+    ReactNativeFlipper.initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
+    ApplicationLifecycleDispatcher.onApplicationCreate(this);
+  }
+
+  @Override
+  public void onConfigurationChanged(@NonNull Configuration newConfig) {
+    super.onConfigurationChanged(newConfig);
+    ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig);
+  }
+}
diff --git a/android/app/src/main/java/xyz/blueskyweb/pubsq/MainActivity.java b/android/app/src/main/java/xyz/blueskyweb/pubsq/MainActivity.java
deleted file mode 100644
index ed44ef8d7..000000000
--- a/android/app/src/main/java/xyz/blueskyweb/pubsq/MainActivity.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package xyz.blueskyweb.app;
-
-import com.facebook.react.ReactActivity;
-import com.facebook.react.ReactActivityDelegate;
-import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
-import com.facebook.react.defaults.DefaultReactActivityDelegate;
-import android.os.Bundle;
-
-public class MainActivity extends ReactActivity {
-
-  /**
-   * Returns the name of the main component registered from JavaScript. This is used to schedule
-   * rendering of the component.
-   */
-  @Override
-  protected String getMainComponentName() {
-    return "xyz.blueskyweb.app";
-  }
-
-  /**
-   * Returns the instance of the {@link ReactActivityDelegate}. Here we use a util class {@link
-   * DefaultReactActivityDelegate} which allows you to easily enable Fabric and Concurrent React
-   * (aka React 18) with two boolean flags.
-   */
-  @Override
-  protected ReactActivityDelegate createReactActivityDelegate() {
-    return new DefaultReactActivityDelegate(
-      this,
-      getMainComponentName(),
-      // If you opted-in for the New Architecture, we enable the Fabric Renderer.
-      DefaultNewArchitectureEntryPoint.getFabricEnabled(), // fabricEnabled
-      // If you opted-in for the New Architecture, we enable Concurrent React (i.e. React 18).
-      DefaultNewArchitectureEntryPoint.getConcurrentReactEnabled() // concurrentRootEnabled
-    );
-  }
-}
diff --git a/android/app/src/main/java/xyz/blueskyweb/pubsq/MainApplication.java b/android/app/src/main/java/xyz/blueskyweb/pubsq/MainApplication.java
deleted file mode 100644
index 34b34b0b2..000000000
--- a/android/app/src/main/java/xyz/blueskyweb/pubsq/MainApplication.java
+++ /dev/null
@@ -1,65 +0,0 @@
-package xyz.blueskyweb.app;
-
-import android.app.Application;
-import com.facebook.react.PackageList;
-import com.facebook.react.ReactApplication;
-import com.facebook.react.ReactNativeHost;
-import com.facebook.react.ReactPackage;
-import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
-import com.facebook.react.defaults.DefaultReactNativeHost;
-import com.facebook.soloader.SoLoader;
-import xyz.blueskyweb.app.newarchitecture.MainApplicationReactNativeHost;
-import java.lang.reflect.InvocationTargetException;
-import java.util.List;
-
-public class MainApplication extends Application implements ReactApplication {
-
-  private final ReactNativeHost mReactNativeHost =
-      new DefaultReactNativeHost(this) {
-        @Override
-        public boolean getUseDeveloperSupport() {
-          return BuildConfig.DEBUG;
-        }
-
-        @Override
-        protected List<ReactPackage> getPackages() {
-          @SuppressWarnings("UnnecessaryLocalVariable")
-          List<ReactPackage> packages = new PackageList(this).getPackages();
-          // Packages that cannot be autolinked yet can be added manually here, for example:
-          // packages.add(new MyReactNativePackage());
-          return packages;
-        }
-
-        @Override
-        protected String getJSMainModuleName() {
-          return "index";
-        }
-
-        @Override
-        protected boolean isNewArchEnabled() {
-          return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
-        }
-        @Override
-        protected Boolean isHermesEnabled() {
-          return BuildConfig.IS_HERMES_ENABLED;
-        }
-      };
-
-  @Override
-  public ReactNativeHost getReactNativeHost() {
-    return mReactNativeHost;
-  }
-
-  @Override
-  public void onCreate() {
-    super.onCreate();
-    // If you opted-in for the New Architecture, we enable the TurboModule system
-    ReactFeatureFlags.useTurboModules = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
-    SoLoader.init(this, /* native exopackage */ false);
-    if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
-      // If you opted-in for the New Architecture, we load the native entry point for this app.
-      DefaultNewArchitectureEntryPoint.load();
-    }
-    ReactNativeFlipper.initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
-  }
-}
diff --git a/android/app/src/main/jni/Android.mk b/android/app/src/main/jni/Android.mk
deleted file mode 100644
index b3099f122..000000000
--- a/android/app/src/main/jni/Android.mk
+++ /dev/null
@@ -1,49 +0,0 @@
-THIS_DIR := $(call my-dir)
-
-include $(REACT_ANDROID_DIR)/Android-prebuilt.mk
-
-# If you wish to add a custom TurboModule or Fabric component in your app you
-# will have to include the following autogenerated makefile.
-# include $(GENERATED_SRC_DIR)/codegen/jni/Android.mk
-include $(CLEAR_VARS)
-
-LOCAL_PATH := $(THIS_DIR)
-
-# You can customize the name of your application .so file here.
-LOCAL_MODULE := app_appmodules
-
-LOCAL_C_INCLUDES := $(LOCAL_PATH)
-LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/*.cpp)
-LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)
-
-# If you wish to add a custom TurboModule or Fabric component in your app you
-# will have to uncomment those lines to include the generated source 
-# files from the codegen (placed in $(GENERATED_SRC_DIR)/codegen/jni)
-#
-# LOCAL_C_INCLUDES += $(GENERATED_SRC_DIR)/codegen/jni
-# LOCAL_SRC_FILES += $(wildcard $(GENERATED_SRC_DIR)/codegen/jni/*.cpp)
-# LOCAL_EXPORT_C_INCLUDES += $(GENERATED_SRC_DIR)/codegen/jni
-
-# Here you should add any native library you wish to depend on.
-LOCAL_SHARED_LIBRARIES := \
-  libfabricjni \
-  libfbjni \
-  libfolly_futures \
-  libfolly_json \
-  libglog \
-  libjsi \
-  libreact_codegen_rncore \
-  libreact_debug \
-  libreact_nativemodule_core \
-  libreact_render_componentregistry \
-  libreact_render_core \
-  libreact_render_debug \
-  libreact_render_graphics \
-  librrc_view \
-  libruntimeexecutor \
-  libturbomodulejsijni \
-  libyoga
-
-LOCAL_CFLAGS := -DLOG_TAG=\"ReactNative\" -fexceptions -frtti -std=c++17 -Wall
-
-include $(BUILD_SHARED_LIBRARY)
diff --git a/android/app/src/release/java/com/xyz/blueskyweb/app/ReactNativeFlipper.java b/android/app/src/release/java/xyz/blueskyweb/app/ReactNativeFlipper.java
index 2c66eae53..2c66eae53 100644
--- a/android/app/src/release/java/com/xyz/blueskyweb/app/ReactNativeFlipper.java
+++ b/android/app/src/release/java/xyz/blueskyweb/app/ReactNativeFlipper.java
diff --git a/android/build.gradle b/android/build.gradle
index c918b0fd8..778287733 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -1,13 +1,15 @@
-import org.apache.tools.ant.taskdefs.condition.Os
-
 // Top-level build file where you can add configuration options common to all sub-projects/modules.
 
 buildscript {
     ext {
-        buildToolsVersion = "33.0.0"
-        minSdkVersion = 21
-        compileSdkVersion = 33
-        targetSdkVersion = 33
+        buildToolsVersion = findProperty('android.buildToolsVersion') ?: '33.0.0'
+        minSdkVersion = Integer.parseInt(findProperty('android.minSdkVersion') ?: '21')
+        compileSdkVersion = Integer.parseInt(findProperty('android.compileSdkVersion') ?: '33')
+        targetSdkVersion = Integer.parseInt(findProperty('android.targetSdkVersion') ?: '33')
+        if (findProperty('android.kotlinVersion')) {
+            kotlinVersion = findProperty('android.kotlinVersion')
+        }
+        frescoVersion = findProperty('expo.frescoVersion') ?: '2.5.0'
 
         // We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP.
         ndkVersion = "23.1.7779620"
@@ -17,7 +19,24 @@ buildscript {
         mavenCentral()
     }
     dependencies {
-        classpath("com.android.tools.build:gradle:7.3.1")
-        classpath("com.facebook.react:react-native-gradle-plugin")
+        classpath('com.android.tools.build:gradle:7.3.1')
+        classpath('com.facebook.react:react-native-gradle-plugin')
+    }
+}
+
+allprojects {
+    repositories {
+        maven {
+            // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
+            url(new File(['node', '--print', "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim(), '../android'))
+        }
+        maven {
+            // Android JSC is installed from npm
+            url(new File(['node', '--print', "require.resolve('jsc-android/package.json')"].execute(null, rootDir).text.trim(), '../dist'))
+        }
+
+        google()
+        mavenCentral()
+        maven { url 'https://www.jitpack.io' }
     }
 }
diff --git a/android/gradle.properties b/android/gradle.properties
index cc450992b..9911ac4af 100644
--- a/android/gradle.properties
+++ b/android/gradle.properties
@@ -21,6 +21,7 @@ org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
 # Android operating system, and which are packaged with your app's APK
 # https://developer.android.com/topic/libraries/support-library/androidx-rn
 android.useAndroidX=true
+
 # Automatically convert third-party libraries to use AndroidX
 android.enableJetifier=true
 
@@ -39,6 +40,14 @@ reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
 # are providing them.
 newArchEnabled=false
 
-# Use this property to enable or disable the Hermes JS engine.
-# If set to false, you will be using JSC instead.
-hermesEnabled=true
\ No newline at end of file
+# The hosted JavaScript engine
+# Supported values: expo.jsEngine = "hermes" | "jsc"
+expo.jsEngine=hermes
+
+# Enable GIF support in React Native images (~200 B increase)
+expo.gif.enabled=true
+# Enable webp support in React Native images (~85 KB increase)
+expo.webp.enabled=true
+# Enable animated webp support (~3.4 MB increase)
+# Disabled by default because iOS doesn't support animated webp
+expo.webp.animated=false
diff --git a/android/gradlew b/android/gradlew
index 1b6c78733..a69d9cb6c 100755
--- a/android/gradlew
+++ b/android/gradlew
@@ -205,6 +205,12 @@ set -- \
         org.gradle.wrapper.GradleWrapperMain \
         "$@"
 
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+    die "xargs is not available"
+fi
+
 # Use "xargs" to parse quoted args.
 #
 # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
diff --git a/android/gradlew.bat b/android/gradlew.bat
index ac1b06f93..53a6b238d 100644
--- a/android/gradlew.bat
+++ b/android/gradlew.bat
@@ -14,7 +14,7 @@
 @rem limitations under the License.

 @rem

 

-@if "%DEBUG%" == "" @echo off

+@if "%DEBUG%"=="" @echo off

 @rem ##########################################################################

 @rem

 @rem  Gradle startup script for Windows

@@ -25,7 +25,7 @@
 if "%OS%"=="Windows_NT" setlocal

 

 set DIRNAME=%~dp0

-if "%DIRNAME%" == "" set DIRNAME=.

+if "%DIRNAME%"=="" set DIRNAME=.

 set APP_BASE_NAME=%~n0

 set APP_HOME=%DIRNAME%

 

@@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
 

 set JAVA_EXE=java.exe

 %JAVA_EXE% -version >NUL 2>&1

-if "%ERRORLEVEL%" == "0" goto execute

+if %ERRORLEVEL% equ 0 goto execute

 

 echo.

 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

@@ -75,13 +75,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
 

 :end

 @rem End local scope for the variables with windows NT shell

-if "%ERRORLEVEL%"=="0" goto mainEnd

+if %ERRORLEVEL% equ 0 goto mainEnd

 

 :fail

 rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of

 rem the _cmd.exe /c_ return code!

-if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1

-exit /b 1

+set EXIT_CODE=%ERRORLEVEL%

+if %EXIT_CODE% equ 0 set EXIT_CODE=1

+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%

+exit /b %EXIT_CODE%

 

 :mainEnd

 if "%OS%"=="Windows_NT" endlocal

diff --git a/android/settings.gradle b/android/settings.gradle
index b55dd118f..24f72d0d6 100644
--- a/android/settings.gradle
+++ b/android/settings.gradle
@@ -1,6 +1,10 @@
-rootProject.name = 'app'
-apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
+rootProject.name = 'bluesky'
+
+apply from: new File(["node", "--print", "require.resolve('expo/package.json')"].execute(null, rootDir).text.trim(), "../scripts/autolinking.gradle");
+useExpoModules()
+
+apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json')"].execute(null, rootDir).text.trim(), "../native_modules.gradle");
+applyNativeModulesSettingsGradle(settings)
+
 include ':app'
-includeBuild('../node_modules/react-native-gradle-plugin')
-include ':rn-fetch-blob'
-project(':rn-fetch-blob').projectDir = new File(rootProject.projectDir, '../node_modules/rn-fetch-blob/android')
+includeBuild(new File(["node", "--print", "require.resolve('react-native-gradle-plugin/package.json')"].execute(null, rootDir).text.trim()).getParentFile())
diff --git a/app.json b/app.json
new file mode 100644
index 000000000..13e35d9a7
--- /dev/null
+++ b/app.json
@@ -0,0 +1,29 @@
+{
+  "expo": {
+    "name": "bluesky",
+    "slug": "bluesky",
+    "version": "1.7.0",
+    "orientation": "portrait",
+    "icon": "./assets/icon.png",
+    "userInterfaceStyle": "light",
+    "splash": {
+      "image": "./assets/splash.png",
+      "resizeMode": "contain",
+      "backgroundColor": "#ffffff"
+    },
+    "ios": {
+      "supportsTablet": false,
+      "bundleIdentifier": "xyz.blueskyweb.app"
+    },
+    "android": {
+      "adaptiveIcon": {
+        "foregroundImage": "./assets/adaptive-icon.png",
+        "backgroundColor": "#ffffff"
+      },
+      "package": "xyz.blueskyweb.app"
+    },
+    "web": {
+      "favicon": "./assets/favicon.png"
+    }
+  }
+}
diff --git a/ios/app/Images.xcassets/AppIcon.appiconset/1024.png b/assets/adaptive-icon.png
index 1dda9a342..1dda9a342 100644
--- a/ios/app/Images.xcassets/AppIcon.appiconset/1024.png
+++ b/assets/adaptive-icon.png
Binary files differdiff --git a/public/img/cloud-splash.png b/assets/cloud-splash.png
index 188625331..188625331 100644
--- a/public/img/cloud-splash.png
+++ b/assets/cloud-splash.png
Binary files differdiff --git a/public/img/default-avatar.jpg b/assets/default-avatar.jpg
index ab141651e..ab141651e 100644
--- a/public/img/default-avatar.jpg
+++ b/assets/default-avatar.jpg
Binary files differdiff --git a/assets/favicon.png b/assets/favicon.png
new file mode 100644
index 000000000..24ec61e0a
--- /dev/null
+++ b/assets/favicon.png
Binary files differdiff --git a/assets/icon.png b/assets/icon.png
new file mode 100644
index 000000000..1dda9a342
--- /dev/null
+++ b/assets/icon.png
Binary files differdiff --git a/public/img/tabs-explainer.jpg b/assets/tabs-explainer.jpg
index 64f0a8fe5..64f0a8fe5 100644
--- a/public/img/tabs-explainer.jpg
+++ b/assets/tabs-explainer.jpg
Binary files differdiff --git a/babel.config.js b/babel.config.js
index fa49ff866..598e2a567 100644
--- a/babel.config.js
+++ b/babel.config.js
@@ -1,31 +1,34 @@
-module.exports = {
-  presets: ['module:metro-react-native-babel-preset'],
-  plugins: [
-    [
-      'module:react-native-dotenv',
-      {
-        envName: 'APP_ENV',
-        moduleName: '@env',
-        path: '.env',
-        blocklist: null,
-        allowlist: null,
-        safe: false,
-        allowUndefined: true,
-        verbose: false,
-      },
-    ],
-    [
-      'module-resolver',
-      {
-        alias: {
-          // This needs to be mirrored in tsconfig.json
-          lib: './src/lib',
-          platform: './src/platform',
-          state: './src/state',
-          view: './src/view',
+module.exports = function (api) {
+  api.cache(true)
+  return {
+    presets: ['babel-preset-expo'],
+    plugins: [
+      [
+        'module:react-native-dotenv',
+        {
+          envName: 'APP_ENV',
+          moduleName: '@env',
+          path: '.env',
+          blocklist: null,
+          allowlist: null,
+          safe: false,
+          allowUndefined: true,
+          verbose: false,
+        },
+      ],
+      [
+        'module-resolver',
+        {
+          alias: {
+            // This needs to be mirrored in tsconfig.json
+            lib: './src/lib',
+            platform: './src/platform',
+            state: './src/state',
+            view: './src/view',
+          },
         },
-      },
+      ],
+      'react-native-reanimated/plugin', // NOTE: this plugin MUST be last
     ],
-    'react-native-reanimated/plugin', // NOTE: this plugin MUST be last
-  ],
+  }
 }
diff --git a/index.js b/index.js
index 1a6b21964..4faf36dc4 100644
--- a/index.js
+++ b/index.js
@@ -1,10 +1,14 @@
-/**
- * @format
- */
+import 'react-native-gesture-handler' // must be first
+
+import {LogBox} from 'react-native'
+LogBox.ignoreLogs(['Require cycle:']) // suppress require-cycle warnings, it's fine
 
 import 'platform/polyfills'
-import {AppRegistry} from 'react-native'
+import {registerRootComponent} from 'expo'
+
 import App from './src/App'
-import {name as appName} from './src/app.json'
 
-AppRegistry.registerComponent(appName, () => App)
+// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
+// It also ensures that whether you load the app in Expo Go or in a native build,
+// the environment is set up appropriately
+registerRootComponent(App)
diff --git a/index.web.js b/index.web.js
index f443dbb6d..4ceb656fb 100644
--- a/index.web.js
+++ b/index.web.js
@@ -1,14 +1,4 @@
-// index.web.js
-
 import 'platform/polyfills'
-import {AppRegistry} from 'react-native'
+import {registerRootComponent} from 'expo'
 import App from './src/App'
-import {name as appName} from './src/app.json'
-
-// register the app
-AppRegistry.registerComponent(appName, () => App)
-
-AppRegistry.runApplication(appName, {
-  initialProps: {},
-  rootTag: document.getElementById('app-root'),
-})
+registerRootComponent(App)
diff --git a/ios/.xcode.env.local b/ios/.xcode.env.local
new file mode 100644
index 000000000..02cdf9420
--- /dev/null
+++ b/ios/.xcode.env.local
@@ -0,0 +1 @@
+export NODE_BINARY="/Users/paulfrazee/.nvm/versions/node/v18.8.0/bin/node"
diff --git a/ios/LaunchScreen.storyboard b/ios/LaunchScreen.storyboard
deleted file mode 100644
index 700f1cfcc..000000000
--- a/ios/LaunchScreen.storyboard
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="Y6W-OH-hqX">
-    <device id="retina6_12" orientation="portrait" appearance="light"/>
-    <dependencies>
-        <deployment identifier="iOS"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
-        <capability name="System colors in document resources" minToolsVersion="11.0"/>
-        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
-    </dependencies>
-    <scenes>
-        <!--View Controller-->
-        <scene sceneID="s0d-6b-0kx">
-            <objects>
-                <viewController id="Y6W-OH-hqX" sceneMemberID="viewController">
-                    <view key="view" contentMode="scaleToFill" id="5EZ-qb-Rvc">
-                        <rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
-                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
-                        <subviews>
-                            <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" image="LaunchScreen" translatesAutoresizingMaskIntoConstraints="NO" id="Ppr-Vi-7AA">
-                                <rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
-                            </imageView>
-                        </subviews>
-                        <color key="backgroundColor" systemColor="systemBackgroundColor"/>
-                        <constraints>
-                            <constraint firstAttribute="trailing" secondItem="Ppr-Vi-7AA" secondAttribute="trailing" id="8NR-sO-ZhN"/>
-                            <constraint firstItem="Ppr-Vi-7AA" firstAttribute="top" secondItem="5EZ-qb-Rvc" secondAttribute="top" id="Od5-pK-zM2"/>
-                            <constraint firstItem="Ppr-Vi-7AA" firstAttribute="leading" secondItem="5EZ-qb-Rvc" secondAttribute="leading" id="Ww6-nx-IYo"/>
-                            <constraint firstAttribute="bottom" secondItem="Ppr-Vi-7AA" secondAttribute="bottom" id="vTP-N3-Ihm"/>
-                        </constraints>
-                    </view>
-                </viewController>
-                <placeholder placeholderIdentifier="IBFirstResponder" id="Ief-a0-LHa" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
-            </objects>
-            <point key="canvasLocation" x="119.84732824427481" y="4.9295774647887329"/>
-        </scene>
-    </scenes>
-    <resources>
-        <image name="LaunchScreen" width="600" height="900"/>
-        <systemColor name="systemBackgroundColor">
-            <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-        </systemColor>
-    </resources>
-</document>
diff --git a/ios/Podfile b/ios/Podfile
index 2e80c1455..70072e51d 100644
--- a/ios/Podfile
+++ b/ios/Podfile
@@ -1,87 +1,91 @@
-require_relative '../node_modules/react-native/scripts/react_native_pods'
-require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
+require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking")
+require File.join(File.dirname(`node --print "require.resolve('react-native/package.json')"`), "scripts/react_native_pods")
+require File.join(File.dirname(`node --print "require.resolve('@react-native-community/cli-platform-ios/package.json')"`), "native_modules")
+
+require 'json'
+podfile_properties = JSON.parse(File.read(File.join(__dir__, 'Podfile.properties.json'))) rescue {}
+
+ENV['RCT_NEW_ARCH_ENABLED'] = podfile_properties['newArchEnabled'] == 'true' ? '1' : '0'
+
+platform :ios, podfile_properties['ios.deploymentTarget'] || '13.0'
+install! 'cocoapods',
+  :deterministic_uuids => false
 
-platform :ios, min_ios_version_supported
 prepare_react_native_project!
 
 # If you are using a `react-native-flipper` your iOS build will fail when `NO_FLIPPER=1` is set.
-# because `react-native-flipper` depends on (FlipperKit,...) that will be excluded
+# because `react-native-flipper` depends on (FlipperKit,...), which will be excluded. To fix this,
+# you can also exclude `react-native-flipper` in `react-native.config.js`
 #
-# To fix this you can also exclude `react-native-flipper` using a `react-native.config.js`
 # ```js
 # module.exports = {
 #   dependencies: {
 #     ...(process.env.NO_FLIPPER ? { 'react-native-flipper': { platforms: { ios: null } } } : {}),
+#   }
+# }
 # ```
-flipper_config = ENV['NO_FLIPPER'] == "1" ? FlipperConfiguration.disabled : FlipperConfiguration.enabled
-
-linkage = ENV['USE_FRAMEWORKS']
-if linkage != nil
-  Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green
-  use_frameworks! :linkage => linkage.to_sym
+flipper_config = FlipperConfiguration.disabled
+if ENV['NO_FLIPPER'] == "1" || ENV['CI'] then
+  # Explicitly disabled through environment variables
+  flipper_config = FlipperConfiguration.disabled
+elsif podfile_properties.key?('ios.flipper') then
+  # Configure Flipper in Podfile.properties.json
+  if podfile_properties['ios.flipper'] == 'true' then
+    flipper_config = FlipperConfiguration.enabled(["Debug", "Release"])
+  elsif ppodfile_properties['ios.flipper'] != 'false' then
+    flipper_config = FlipperConfiguration.enabled(["Debug", "Release"], { 'Flipper' => podfile_properties['ios.flipper'] })
+  end
 end
 
-target 'app' do
-  pod 'React-RCTLinking', :path => '../node_modules/react-native/Libraries/LinkingIOS'
+
+
+target 'bluesky' do
+  use_expo_modules!
   config = use_native_modules!
 
+  use_frameworks! :linkage => podfile_properties['ios.useFrameworks'].to_sym if podfile_properties['ios.useFrameworks']
+  use_frameworks! :linkage => ENV['USE_FRAMEWORKS'].to_sym if ENV['USE_FRAMEWORKS']
+
   # Flags change depending on the env values.
   flags = get_default_flags()
 
   use_react_native!(
     :path => config[:reactNativePath],
-    # Hermes is now enabled by default. Disable by setting this flag to false.
-    # Upcoming versions of React Native may rely on get_default_flags(), but
-    # we make it explicit here to aid in the React Native upgrade process.
-    :hermes_enabled => flags[:hermes_enabled],
+    :hermes_enabled => podfile_properties['expo.jsEngine'] == nil || podfile_properties['expo.jsEngine'] == 'hermes',
     :fabric_enabled => flags[:fabric_enabled],
-    # Enables Flipper.
-    #
-    # Note that if you have use_frameworks! enabled, Flipper will not work and
-    # you should disable the next line.
-    #:flipper_configuration => flipper_config,
     # An absolute path to your application root.
-    :app_path => "#{Pod::Config.instance.installation_root}/.."
+    :app_path => "#{Pod::Config.instance.installation_root}/..",
+    # Note that if you have use_frameworks! enabled, Flipper will not work if enabled
+    :flipper_configuration => flipper_config
   )
 
-  # react-native-permissions settings
-  permissions_path = '../node_modules/react-native-permissions/ios'
-  pod 'Permission-Camera', :path => "#{permissions_path}/Camera"
-  pod 'Permission-PhotoLibrary', :path => "#{permissions_path}/PhotoLibrary"
-
-  target 'appTests' do
-    inherit! :complete
-    # Pods for testing
-  end
-
   post_install do |installer|
-    # Temporary fix until CocoaPods 1.12.0 is released.
-    # https://github.com/CocoaPods/CocoaPods/issues/11402#issuecomment-1201464693
-    installer.pods_project.targets.each do |target|
-      if target.respond_to?(:product_type) and target.product_type == "com.apple.product-type.bundle"
-        target.build_configurations.each do |config|
-            config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO'
-        end
-      end
-    end  
-
-
     react_native_post_install(
       installer,
+      config[:reactNativePath],
       # Set `mac_catalyst_enabled` to `true` in order to apply patches
       # necessary for Mac Catalyst builds
       :mac_catalyst_enabled => false
     )
     __apply_Xcode_12_5_M1_post_install_workaround(installer)
 
-    # iOS fix
-    installer.pods_project.build_configurations.each do |config|
-      config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64"
-    end
-    installer.pods_project.targets.each do |target|
-      target.build_configurations.each do |config|
-        config.build_settings['ONLY_ACTIVE_ARCH'] = 'YES'
+    # This is necessary for Xcode 14, because it signs resource bundles by default
+    # when building for devices.
+    installer.target_installation_results.pod_target_installation_results
+      .each do |pod_name, target_installation_result|
+      target_installation_result.resource_bundle_targets.each do |resource_bundle_target|
+        resource_bundle_target.build_configurations.each do |config|
+          config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO'
+        end
       end
     end
   end
-end
+
+  post_integrate do |installer|
+    begin
+      expo_patch_react_imports!(installer)
+    rescue => e
+      Pod::UI.warn e
+    end
+  end
+end
\ No newline at end of file
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index c9d0aee02..dcfef41b0 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -3,19 +3,124 @@ PODS:
   - BVLinearGradient (2.6.2):
     - React-Core
   - DoubleConversion (1.1.6)
-  - FBLazyVector (0.71.1)
-  - FBReactNativeSpec (0.71.1):
+  - EXApplication (5.1.1):
+    - ExpoModulesCore
+  - EXCamera (13.2.1):
+    - ExpoModulesCore
+  - EXConstants (14.2.1):
+    - ExpoModulesCore
+  - EXFileSystem (15.2.2):
+    - ExpoModulesCore
+  - EXFont (11.1.1):
+    - ExpoModulesCore
+  - EXImageLoader (4.1.1):
+    - ExpoModulesCore
+    - React-Core
+  - EXJSONUtils (0.5.1)
+  - EXManifests (0.5.2):
+    - EXJSONUtils
+  - EXMediaLibrary (15.2.2):
+    - ExpoModulesCore
+    - React-Core
+  - Expo (48.0.6):
+    - ExpoModulesCore
+  - expo-dev-client (2.1.5):
+    - EXManifests
+    - expo-dev-launcher
+    - expo-dev-menu
+    - expo-dev-menu-interface
+    - EXUpdatesInterface
+  - expo-dev-launcher (2.1.5):
+    - EXManifests
+    - expo-dev-launcher/Main (= 2.1.5)
+    - expo-dev-menu
+    - expo-dev-menu-interface
+    - ExpoModulesCore
+    - EXUpdatesInterface
+    - React-Core
+  - expo-dev-launcher/Main (2.1.5):
+    - EXManifests
+    - expo-dev-launcher/Unsafe
+    - expo-dev-menu
+    - expo-dev-menu-interface
+    - ExpoModulesCore
+    - EXUpdatesInterface
+    - React-Core
+  - expo-dev-launcher/Unsafe (2.1.5):
+    - EXManifests
+    - expo-dev-menu
+    - expo-dev-menu-interface
+    - ExpoModulesCore
+    - EXUpdatesInterface
+    - React-Core
+  - expo-dev-menu (2.1.3):
+    - expo-dev-menu/Main (= 2.1.3)
+  - expo-dev-menu-interface (1.1.1)
+  - expo-dev-menu/GestureHandler (2.1.3)
+  - expo-dev-menu/Main (2.1.3):
+    - EXManifests
+    - expo-dev-menu-interface
+    - expo-dev-menu/Vendored
+    - ExpoModulesCore
+    - React-Core
+  - expo-dev-menu/Reanimated (2.1.3):
+    - DoubleConversion
+    - FBLazyVector
+    - FBReactNativeSpec
+    - glog
+    - RCT-Folly
+    - RCTRequired
+    - RCTTypeSafety
+    - React-callinvoker
+    - React-Core
+    - React-Core/DevSupport
+    - React-Core/RCTWebSocket
+    - React-CoreModules
+    - React-cxxreact
+    - React-jsi
+    - React-jsiexecutor
+    - React-jsinspector
+    - React-RCTActionSheet
+    - React-RCTAnimation
+    - React-RCTBlob
+    - React-RCTImage
+    - React-RCTLinking
+    - React-RCTNetwork
+    - React-RCTSettings
+    - React-RCTText
+    - React-RCTVibration
+    - ReactCommon/turbomodule/core
+    - Yoga
+  - expo-dev-menu/SafeAreaView (2.1.3)
+  - expo-dev-menu/Vendored (2.1.3):
+    - expo-dev-menu/GestureHandler
+    - expo-dev-menu/Reanimated
+    - expo-dev-menu/SafeAreaView
+  - ExpoImagePicker (14.1.1):
+    - ExpoModulesCore
+  - ExpoKeepAwake (12.0.1):
+    - ExpoModulesCore
+  - ExpoModulesCore (1.2.4):
+    - React-Core
+    - React-RCTAppDelegate
+    - ReactCommon/turbomodule/core
+  - EXSplashScreen (0.18.1):
+    - ExpoModulesCore
+    - React-Core
+  - EXUpdatesInterface (0.9.1)
+  - FBLazyVector (0.71.3)
+  - FBReactNativeSpec (0.71.3):
     - RCT-Folly (= 2021.07.22.00)
-    - RCTRequired (= 0.71.1)
-    - RCTTypeSafety (= 0.71.1)
-    - React-Core (= 0.71.1)
-    - React-jsi (= 0.71.1)
-    - ReactCommon/turbomodule/core (= 0.71.1)
+    - RCTRequired (= 0.71.3)
+    - RCTTypeSafety (= 0.71.3)
+    - React-Core (= 0.71.3)
+    - React-jsi (= 0.71.3)
+    - ReactCommon/turbomodule/core (= 0.71.3)
   - fmt (6.2.1)
   - glog (0.3.5)
-  - hermes-engine (0.71.1):
-    - hermes-engine/Pre-built (= 0.71.1)
-  - hermes-engine/Pre-built (0.71.1)
+  - hermes-engine (0.71.3):
+    - hermes-engine/Pre-built (= 0.71.3)
+  - hermes-engine/Pre-built (0.71.3)
   - libevent (2.1.12)
   - libwebp (1.2.4):
     - libwebp/demux (= 1.2.4)
@@ -26,10 +131,6 @@ PODS:
   - libwebp/mux (1.2.4):
     - libwebp/demux
   - libwebp/webp (1.2.4)
-  - Permission-Camera (3.7.2):
-    - RNPermissions
-  - Permission-PhotoLibrary (3.7.2):
-    - RNPermissions
   - RCT-Folly (2021.07.22.00):
     - boost
     - DoubleConversion
@@ -47,26 +148,26 @@ PODS:
     - fmt (~> 6.2.1)
     - glog
     - libevent
-  - RCTRequired (0.71.1)
-  - RCTTypeSafety (0.71.1):
-    - FBLazyVector (= 0.71.1)
-    - RCTRequired (= 0.71.1)
-    - React-Core (= 0.71.1)
-  - React (0.71.1):
-    - React-Core (= 0.71.1)
-    - React-Core/DevSupport (= 0.71.1)
-    - React-Core/RCTWebSocket (= 0.71.1)
-    - React-RCTActionSheet (= 0.71.1)
-    - React-RCTAnimation (= 0.71.1)
-    - React-RCTBlob (= 0.71.1)
-    - React-RCTImage (= 0.71.1)
-    - React-RCTLinking (= 0.71.1)
-    - React-RCTNetwork (= 0.71.1)
-    - React-RCTSettings (= 0.71.1)
-    - React-RCTText (= 0.71.1)
-    - React-RCTVibration (= 0.71.1)
-  - React-callinvoker (0.71.1)
-  - React-Codegen (0.71.1):
+  - RCTRequired (0.71.3)
+  - RCTTypeSafety (0.71.3):
+    - FBLazyVector (= 0.71.3)
+    - RCTRequired (= 0.71.3)
+    - React-Core (= 0.71.3)
+  - React (0.71.3):
+    - React-Core (= 0.71.3)
+    - React-Core/DevSupport (= 0.71.3)
+    - React-Core/RCTWebSocket (= 0.71.3)
+    - React-RCTActionSheet (= 0.71.3)
+    - React-RCTAnimation (= 0.71.3)
+    - React-RCTBlob (= 0.71.3)
+    - React-RCTImage (= 0.71.3)
+    - React-RCTLinking (= 0.71.3)
+    - React-RCTNetwork (= 0.71.3)
+    - React-RCTSettings (= 0.71.3)
+    - React-RCTText (= 0.71.3)
+    - React-RCTVibration (= 0.71.3)
+  - React-callinvoker (0.71.3)
+  - React-Codegen (0.71.3):
     - FBReactNativeSpec
     - hermes-engine
     - RCT-Folly
@@ -77,177 +178,209 @@ PODS:
     - React-jsiexecutor
     - ReactCommon/turbomodule/bridging
     - ReactCommon/turbomodule/core
-  - React-Core (0.71.1):
+  - React-Core (0.71.3):
     - glog
+    - hermes-engine
     - RCT-Folly (= 2021.07.22.00)
-    - React-Core/Default (= 0.71.1)
-    - React-cxxreact (= 0.71.1)
-    - React-jsi (= 0.71.1)
-    - React-jsiexecutor (= 0.71.1)
-    - React-perflogger (= 0.71.1)
+    - React-Core/Default (= 0.71.3)
+    - React-cxxreact (= 0.71.3)
+    - React-hermes
+    - React-jsi (= 0.71.3)
+    - React-jsiexecutor (= 0.71.3)
+    - React-perflogger (= 0.71.3)
     - Yoga
-  - React-Core/CoreModulesHeaders (0.71.1):
+  - React-Core/CoreModulesHeaders (0.71.3):
     - glog
+    - hermes-engine
     - RCT-Folly (= 2021.07.22.00)
     - React-Core/Default
-    - React-cxxreact (= 0.71.1)
-    - React-jsi (= 0.71.1)
-    - React-jsiexecutor (= 0.71.1)
-    - React-perflogger (= 0.71.1)
+    - React-cxxreact (= 0.71.3)
+    - React-hermes
+    - React-jsi (= 0.71.3)
+    - React-jsiexecutor (= 0.71.3)
+    - React-perflogger (= 0.71.3)
     - Yoga
-  - React-Core/Default (0.71.1):
+  - React-Core/Default (0.71.3):
     - glog
+    - hermes-engine
     - RCT-Folly (= 2021.07.22.00)
-    - React-cxxreact (= 0.71.1)
-    - React-jsi (= 0.71.1)
-    - React-jsiexecutor (= 0.71.1)
-    - React-perflogger (= 0.71.1)
+    - React-cxxreact (= 0.71.3)
+    - React-hermes
+    - React-jsi (= 0.71.3)
+    - React-jsiexecutor (= 0.71.3)
+    - React-perflogger (= 0.71.3)
     - Yoga
-  - React-Core/DevSupport (0.71.1):
+  - React-Core/DevSupport (0.71.3):
     - glog
+    - hermes-engine
     - RCT-Folly (= 2021.07.22.00)
-    - React-Core/Default (= 0.71.1)
-    - React-Core/RCTWebSocket (= 0.71.1)
-    - React-cxxreact (= 0.71.1)
-    - React-jsi (= 0.71.1)
-    - React-jsiexecutor (= 0.71.1)
-    - React-jsinspector (= 0.71.1)
-    - React-perflogger (= 0.71.1)
+    - React-Core/Default (= 0.71.3)
+    - React-Core/RCTWebSocket (= 0.71.3)
+    - React-cxxreact (= 0.71.3)
+    - React-hermes
+    - React-jsi (= 0.71.3)
+    - React-jsiexecutor (= 0.71.3)
+    - React-jsinspector (= 0.71.3)
+    - React-perflogger (= 0.71.3)
     - Yoga
-  - React-Core/RCTActionSheetHeaders (0.71.1):
+  - React-Core/RCTActionSheetHeaders (0.71.3):
     - glog
+    - hermes-engine
     - RCT-Folly (= 2021.07.22.00)
     - React-Core/Default
-    - React-cxxreact (= 0.71.1)
-    - React-jsi (= 0.71.1)
-    - React-jsiexecutor (= 0.71.1)
-    - React-perflogger (= 0.71.1)
+    - React-cxxreact (= 0.71.3)
+    - React-hermes
+    - React-jsi (= 0.71.3)
+    - React-jsiexecutor (= 0.71.3)
+    - React-perflogger (= 0.71.3)
     - Yoga
-  - React-Core/RCTAnimationHeaders (0.71.1):
+  - React-Core/RCTAnimationHeaders (0.71.3):
     - glog
+    - hermes-engine
     - RCT-Folly (= 2021.07.22.00)
     - React-Core/Default
-    - React-cxxreact (= 0.71.1)
-    - React-jsi (= 0.71.1)
-    - React-jsiexecutor (= 0.71.1)
-    - React-perflogger (= 0.71.1)
+    - React-cxxreact (= 0.71.3)
+    - React-hermes
+    - React-jsi (= 0.71.3)
+    - React-jsiexecutor (= 0.71.3)
+    - React-perflogger (= 0.71.3)
     - Yoga
-  - React-Core/RCTBlobHeaders (0.71.1):
+  - React-Core/RCTBlobHeaders (0.71.3):
     - glog
+    - hermes-engine
     - RCT-Folly (= 2021.07.22.00)
     - React-Core/Default
-    - React-cxxreact (= 0.71.1)
-    - React-jsi (= 0.71.1)
-    - React-jsiexecutor (= 0.71.1)
-    - React-perflogger (= 0.71.1)
+    - React-cxxreact (= 0.71.3)
+    - React-hermes
+    - React-jsi (= 0.71.3)
+    - React-jsiexecutor (= 0.71.3)
+    - React-perflogger (= 0.71.3)
     - Yoga
-  - React-Core/RCTImageHeaders (0.71.1):
+  - React-Core/RCTImageHeaders (0.71.3):
     - glog
+    - hermes-engine
     - RCT-Folly (= 2021.07.22.00)
     - React-Core/Default
-    - React-cxxreact (= 0.71.1)
-    - React-jsi (= 0.71.1)
-    - React-jsiexecutor (= 0.71.1)
-    - React-perflogger (= 0.71.1)
+    - React-cxxreact (= 0.71.3)
+    - React-hermes
+    - React-jsi (= 0.71.3)
+    - React-jsiexecutor (= 0.71.3)
+    - React-perflogger (= 0.71.3)
     - Yoga
-  - React-Core/RCTLinkingHeaders (0.71.1):
+  - React-Core/RCTLinkingHeaders (0.71.3):
     - glog
+    - hermes-engine
     - RCT-Folly (= 2021.07.22.00)
     - React-Core/Default
-    - React-cxxreact (= 0.71.1)
-    - React-jsi (= 0.71.1)
-    - React-jsiexecutor (= 0.71.1)
-    - React-perflogger (= 0.71.1)
+    - React-cxxreact (= 0.71.3)
+    - React-hermes
+    - React-jsi (= 0.71.3)
+    - React-jsiexecutor (= 0.71.3)
+    - React-perflogger (= 0.71.3)
     - Yoga
-  - React-Core/RCTNetworkHeaders (0.71.1):
+  - React-Core/RCTNetworkHeaders (0.71.3):
     - glog
+    - hermes-engine
     - RCT-Folly (= 2021.07.22.00)
     - React-Core/Default
-    - React-cxxreact (= 0.71.1)
-    - React-jsi (= 0.71.1)
-    - React-jsiexecutor (= 0.71.1)
-    - React-perflogger (= 0.71.1)
+    - React-cxxreact (= 0.71.3)
+    - React-hermes
+    - React-jsi (= 0.71.3)
+    - React-jsiexecutor (= 0.71.3)
+    - React-perflogger (= 0.71.3)
     - Yoga
-  - React-Core/RCTSettingsHeaders (0.71.1):
+  - React-Core/RCTSettingsHeaders (0.71.3):
     - glog
+    - hermes-engine
     - RCT-Folly (= 2021.07.22.00)
     - React-Core/Default
-    - React-cxxreact (= 0.71.1)
-    - React-jsi (= 0.71.1)
-    - React-jsiexecutor (= 0.71.1)
-    - React-perflogger (= 0.71.1)
+    - React-cxxreact (= 0.71.3)
+    - React-hermes
+    - React-jsi (= 0.71.3)
+    - React-jsiexecutor (= 0.71.3)
+    - React-perflogger (= 0.71.3)
     - Yoga
-  - React-Core/RCTTextHeaders (0.71.1):
+  - React-Core/RCTTextHeaders (0.71.3):
     - glog
+    - hermes-engine
     - RCT-Folly (= 2021.07.22.00)
     - React-Core/Default
-    - React-cxxreact (= 0.71.1)
-    - React-jsi (= 0.71.1)
-    - React-jsiexecutor (= 0.71.1)
-    - React-perflogger (= 0.71.1)
+    - React-cxxreact (= 0.71.3)
+    - React-hermes
+    - React-jsi (= 0.71.3)
+    - React-jsiexecutor (= 0.71.3)
+    - React-perflogger (= 0.71.3)
     - Yoga
-  - React-Core/RCTVibrationHeaders (0.71.1):
+  - React-Core/RCTVibrationHeaders (0.71.3):
     - glog
+    - hermes-engine
     - RCT-Folly (= 2021.07.22.00)
     - React-Core/Default
-    - React-cxxreact (= 0.71.1)
-    - React-jsi (= 0.71.1)
-    - React-jsiexecutor (= 0.71.1)
-    - React-perflogger (= 0.71.1)
+    - React-cxxreact (= 0.71.3)
+    - React-hermes
+    - React-jsi (= 0.71.3)
+    - React-jsiexecutor (= 0.71.3)
+    - React-perflogger (= 0.71.3)
     - Yoga
-  - React-Core/RCTWebSocket (0.71.1):
+  - React-Core/RCTWebSocket (0.71.3):
     - glog
+    - hermes-engine
     - RCT-Folly (= 2021.07.22.00)
-    - React-Core/Default (= 0.71.1)
-    - React-cxxreact (= 0.71.1)
-    - React-jsi (= 0.71.1)
-    - React-jsiexecutor (= 0.71.1)
-    - React-perflogger (= 0.71.1)
+    - React-Core/Default (= 0.71.3)
+    - React-cxxreact (= 0.71.3)
+    - React-hermes
+    - React-jsi (= 0.71.3)
+    - React-jsiexecutor (= 0.71.3)
+    - React-perflogger (= 0.71.3)
     - Yoga
-  - React-CoreModules (0.71.1):
+  - React-CoreModules (0.71.3):
     - RCT-Folly (= 2021.07.22.00)
-    - RCTTypeSafety (= 0.71.1)
-    - React-Codegen (= 0.71.1)
-    - React-Core/CoreModulesHeaders (= 0.71.1)
-    - React-jsi (= 0.71.1)
-    - React-RCTImage (= 0.71.1)
-    - ReactCommon/turbomodule/core (= 0.71.1)
-  - React-cxxreact (0.71.1):
+    - RCTTypeSafety (= 0.71.3)
+    - React-Codegen (= 0.71.3)
+    - React-Core/CoreModulesHeaders (= 0.71.3)
+    - React-jsi (= 0.71.3)
+    - React-RCTBlob
+    - React-RCTImage (= 0.71.3)
+    - ReactCommon/turbomodule/core (= 0.71.3)
+  - React-cxxreact (0.71.3):
     - boost (= 1.76.0)
     - DoubleConversion
     - glog
+    - hermes-engine
     - RCT-Folly (= 2021.07.22.00)
-    - React-callinvoker (= 0.71.1)
-    - React-jsi (= 0.71.1)
-    - React-jsinspector (= 0.71.1)
-    - React-logger (= 0.71.1)
-    - React-perflogger (= 0.71.1)
-    - React-runtimeexecutor (= 0.71.1)
-  - React-hermes (0.71.1):
+    - React-callinvoker (= 0.71.3)
+    - React-jsi (= 0.71.3)
+    - React-jsinspector (= 0.71.3)
+    - React-logger (= 0.71.3)
+    - React-perflogger (= 0.71.3)
+    - React-runtimeexecutor (= 0.71.3)
+  - React-hermes (0.71.3):
     - DoubleConversion
     - glog
     - hermes-engine
     - RCT-Folly (= 2021.07.22.00)
     - RCT-Folly/Futures (= 2021.07.22.00)
-    - React-cxxreact (= 0.71.1)
-    - React-jsiexecutor (= 0.71.1)
-    - React-jsinspector (= 0.71.1)
-    - React-perflogger (= 0.71.1)
-  - React-jsi (0.71.1):
+    - React-cxxreact (= 0.71.3)
+    - React-jsi
+    - React-jsiexecutor (= 0.71.3)
+    - React-jsinspector (= 0.71.3)
+    - React-perflogger (= 0.71.3)
+  - React-jsi (0.71.3):
     - boost (= 1.76.0)
     - DoubleConversion
     - glog
     - hermes-engine
     - RCT-Folly (= 2021.07.22.00)
-  - React-jsiexecutor (0.71.1):
+  - React-jsiexecutor (0.71.3):
     - DoubleConversion
     - glog
+    - hermes-engine
     - RCT-Folly (= 2021.07.22.00)
-    - React-cxxreact (= 0.71.1)
-    - React-jsi (= 0.71.1)
-    - React-perflogger (= 0.71.1)
-  - React-jsinspector (0.71.1)
-  - React-logger (0.71.1):
+    - React-cxxreact (= 0.71.3)
+    - React-jsi (= 0.71.3)
+    - React-perflogger (= 0.71.3)
+  - React-jsinspector (0.71.3)
+  - React-logger (0.71.3):
     - glog
   - react-native-blur (4.3.0):
     - React-Core
@@ -255,8 +388,6 @@ PODS:
     - React-Core
   - react-native-image-resizer (3.0.5):
     - React-Core
-  - react-native-pager-view (6.1.4):
-    - React-Core
   - react-native-paste-input (0.6.2):
     - React-Core
     - Swime (= 3.0.6)
@@ -270,89 +401,92 @@ PODS:
     - React-Core
   - react-native-version-number (0.3.6):
     - React
-  - react-native-webview (11.26.1):
+  - react-native-webview (11.26.0):
     - React-Core
-  - React-perflogger (0.71.1)
-  - React-RCTActionSheet (0.71.1):
-    - React-Core/RCTActionSheetHeaders (= 0.71.1)
-  - React-RCTAnimation (0.71.1):
+  - React-perflogger (0.71.3)
+  - React-RCTActionSheet (0.71.3):
+    - React-Core/RCTActionSheetHeaders (= 0.71.3)
+  - React-RCTAnimation (0.71.3):
     - RCT-Folly (= 2021.07.22.00)
-    - RCTTypeSafety (= 0.71.1)
-    - React-Codegen (= 0.71.1)
-    - React-Core/RCTAnimationHeaders (= 0.71.1)
-    - React-jsi (= 0.71.1)
-    - ReactCommon/turbomodule/core (= 0.71.1)
-  - React-RCTAppDelegate (0.71.1):
+    - RCTTypeSafety (= 0.71.3)
+    - React-Codegen (= 0.71.3)
+    - React-Core/RCTAnimationHeaders (= 0.71.3)
+    - React-jsi (= 0.71.3)
+    - ReactCommon/turbomodule/core (= 0.71.3)
+  - React-RCTAppDelegate (0.71.3):
     - RCT-Folly
     - RCTRequired
     - RCTTypeSafety
     - React-Core
     - ReactCommon/turbomodule/core
-  - React-RCTBlob (0.71.1):
+  - React-RCTBlob (0.71.3):
+    - hermes-engine
     - RCT-Folly (= 2021.07.22.00)
-    - React-Codegen (= 0.71.1)
-    - React-Core/RCTBlobHeaders (= 0.71.1)
-    - React-Core/RCTWebSocket (= 0.71.1)
-    - React-jsi (= 0.71.1)
-    - React-RCTNetwork (= 0.71.1)
-    - ReactCommon/turbomodule/core (= 0.71.1)
-  - React-RCTImage (0.71.1):
+    - React-Codegen (= 0.71.3)
+    - React-Core/RCTBlobHeaders (= 0.71.3)
+    - React-Core/RCTWebSocket (= 0.71.3)
+    - React-jsi (= 0.71.3)
+    - React-RCTNetwork (= 0.71.3)
+    - ReactCommon/turbomodule/core (= 0.71.3)
+  - React-RCTImage (0.71.3):
     - RCT-Folly (= 2021.07.22.00)
-    - RCTTypeSafety (= 0.71.1)
-    - React-Codegen (= 0.71.1)
-    - React-Core/RCTImageHeaders (= 0.71.1)
-    - React-jsi (= 0.71.1)
-    - React-RCTNetwork (= 0.71.1)
-    - ReactCommon/turbomodule/core (= 0.71.1)
-  - React-RCTLinking (0.71.1):
-    - React-Codegen (= 0.71.1)
-    - React-Core/RCTLinkingHeaders (= 0.71.1)
-    - React-jsi (= 0.71.1)
-    - ReactCommon/turbomodule/core (= 0.71.1)
-  - React-RCTNetwork (0.71.1):
+    - RCTTypeSafety (= 0.71.3)
+    - React-Codegen (= 0.71.3)
+    - React-Core/RCTImageHeaders (= 0.71.3)
+    - React-jsi (= 0.71.3)
+    - React-RCTNetwork (= 0.71.3)
+    - ReactCommon/turbomodule/core (= 0.71.3)
+  - React-RCTLinking (0.71.3):
+    - React-Codegen (= 0.71.3)
+    - React-Core/RCTLinkingHeaders (= 0.71.3)
+    - React-jsi (= 0.71.3)
+    - ReactCommon/turbomodule/core (= 0.71.3)
+  - React-RCTNetwork (0.71.3):
     - RCT-Folly (= 2021.07.22.00)
-    - RCTTypeSafety (= 0.71.1)
-    - React-Codegen (= 0.71.1)
-    - React-Core/RCTNetworkHeaders (= 0.71.1)
-    - React-jsi (= 0.71.1)
-    - ReactCommon/turbomodule/core (= 0.71.1)
-  - React-RCTSettings (0.71.1):
+    - RCTTypeSafety (= 0.71.3)
+    - React-Codegen (= 0.71.3)
+    - React-Core/RCTNetworkHeaders (= 0.71.3)
+    - React-jsi (= 0.71.3)
+    - ReactCommon/turbomodule/core (= 0.71.3)
+  - React-RCTSettings (0.71.3):
     - RCT-Folly (= 2021.07.22.00)
-    - RCTTypeSafety (= 0.71.1)
-    - React-Codegen (= 0.71.1)
-    - React-Core/RCTSettingsHeaders (= 0.71.1)
-    - React-jsi (= 0.71.1)
-    - ReactCommon/turbomodule/core (= 0.71.1)
-  - React-RCTText (0.71.1):
-    - React-Core/RCTTextHeaders (= 0.71.1)
-  - React-RCTVibration (0.71.1):
+    - RCTTypeSafety (= 0.71.3)
+    - React-Codegen (= 0.71.3)
+    - React-Core/RCTSettingsHeaders (= 0.71.3)
+    - React-jsi (= 0.71.3)
+    - ReactCommon/turbomodule/core (= 0.71.3)
+  - React-RCTText (0.71.3):
+    - React-Core/RCTTextHeaders (= 0.71.3)
+  - React-RCTVibration (0.71.3):
     - RCT-Folly (= 2021.07.22.00)
-    - React-Codegen (= 0.71.1)
-    - React-Core/RCTVibrationHeaders (= 0.71.1)
-    - React-jsi (= 0.71.1)
-    - ReactCommon/turbomodule/core (= 0.71.1)
-  - React-runtimeexecutor (0.71.1):
-    - React-jsi (= 0.71.1)
-  - ReactCommon/turbomodule/bridging (0.71.1):
+    - React-Codegen (= 0.71.3)
+    - React-Core/RCTVibrationHeaders (= 0.71.3)
+    - React-jsi (= 0.71.3)
+    - ReactCommon/turbomodule/core (= 0.71.3)
+  - React-runtimeexecutor (0.71.3):
+    - React-jsi (= 0.71.3)
+  - ReactCommon/turbomodule/bridging (0.71.3):
     - DoubleConversion
     - glog
+    - hermes-engine
     - RCT-Folly (= 2021.07.22.00)
-    - React-callinvoker (= 0.71.1)
-    - React-Core (= 0.71.1)
-    - React-cxxreact (= 0.71.1)
-    - React-jsi (= 0.71.1)
-    - React-logger (= 0.71.1)
-    - React-perflogger (= 0.71.1)
-  - ReactCommon/turbomodule/core (0.71.1):
+    - React-callinvoker (= 0.71.3)
+    - React-Core (= 0.71.3)
+    - React-cxxreact (= 0.71.3)
+    - React-jsi (= 0.71.3)
+    - React-logger (= 0.71.3)
+    - React-perflogger (= 0.71.3)
+  - ReactCommon/turbomodule/core (0.71.3):
     - DoubleConversion
     - glog
+    - hermes-engine
     - RCT-Folly (= 2021.07.22.00)
-    - React-callinvoker (= 0.71.1)
-    - React-Core (= 0.71.1)
-    - React-cxxreact (= 0.71.1)
-    - React-jsi (= 0.71.1)
-    - React-logger (= 0.71.1)
-    - React-perflogger (= 0.71.1)
+    - React-callinvoker (= 0.71.3)
+    - React-Core (= 0.71.3)
+    - React-cxxreact (= 0.71.3)
+    - React-jsi (= 0.71.3)
+    - React-logger (= 0.71.3)
+    - React-perflogger (= 0.71.3)
   - rn-fetch-blob (0.12.0):
     - React-Core
   - RNBackgroundFetch (4.1.8):
@@ -385,8 +519,6 @@ PODS:
     - RNNotifee/NotifeeCore (= 7.5.0)
   - RNNotifee/NotifeeCore (7.5.0):
     - React-Core
-  - RNPermissions (3.7.2):
-    - React-Core
   - RNReactNativeHapticFeedback (1.14.0):
     - React-Core
   - RNReanimated (2.14.4):
@@ -419,7 +551,7 @@ PODS:
   - RNScreens (3.20.0):
     - React-Core
     - React-RCTImage
-  - RNSVG (12.5.1):
+  - RNSVG (13.8.0):
     - React-Core
   - SDWebImage (5.11.1):
     - SDWebImage/Core (= 5.11.1)
@@ -427,7 +559,7 @@ PODS:
   - SDWebImageWebPCoder (0.8.5):
     - libwebp (~> 1.0)
     - SDWebImage/Core (~> 5.10)
-  - segment-analytics-react-native (2.13.0):
+  - segment-analytics-react-native (2.13.1):
     - React-Core
     - sovran-react-native
   - sovran-react-native (0.4.5):
@@ -440,13 +572,30 @@ DEPENDENCIES:
   - boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`)
   - BVLinearGradient (from `../node_modules/react-native-linear-gradient`)
   - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
+  - EXApplication (from `../node_modules/expo-application/ios`)
+  - EXCamera (from `../node_modules/expo-camera/ios`)
+  - EXConstants (from `../node_modules/expo-constants/ios`)
+  - EXFileSystem (from `../node_modules/expo-file-system/ios`)
+  - EXFont (from `../node_modules/expo-font/ios`)
+  - EXImageLoader (from `../node_modules/expo-image-loader/ios`)
+  - EXJSONUtils (from `../node_modules/expo-json-utils/ios`)
+  - EXManifests (from `../node_modules/expo-manifests/ios`)
+  - EXMediaLibrary (from `../node_modules/expo-media-library/ios`)
+  - Expo (from `../node_modules/expo`)
+  - expo-dev-client (from `../node_modules/expo-dev-client/ios`)
+  - expo-dev-launcher (from `../node_modules/expo-dev-launcher`)
+  - expo-dev-menu (from `../node_modules/expo-dev-menu`)
+  - expo-dev-menu-interface (from `../node_modules/expo-dev-menu-interface/ios`)
+  - ExpoImagePicker (from `../node_modules/expo-image-picker/ios`)
+  - ExpoKeepAwake (from `../node_modules/expo-keep-awake/ios`)
+  - ExpoModulesCore (from `../node_modules/expo-modules-core`)
+  - EXSplashScreen (from `../node_modules/expo-splash-screen/ios`)
+  - EXUpdatesInterface (from `../node_modules/expo-updates-interface/ios`)
   - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`)
   - FBReactNativeSpec (from `../node_modules/react-native/React/FBReactNativeSpec`)
   - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
   - hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`)
   - libevent (~> 2.1.12)
-  - Permission-Camera (from `../node_modules/react-native-permissions/ios/Camera`)
-  - Permission-PhotoLibrary (from `../node_modules/react-native-permissions/ios/PhotoLibrary`)
   - RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
   - RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`)
   - RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`)
@@ -465,7 +614,6 @@ DEPENDENCIES:
   - "react-native-blur (from `../node_modules/@react-native-community/blur`)"
   - "react-native-cameraroll (from `../node_modules/@react-native-camera-roll/camera-roll`)"
   - "react-native-image-resizer (from `../node_modules/@bam.tech/react-native-image-resizer`)"
-  - react-native-pager-view (from `../node_modules/react-native-pager-view`)
   - "react-native-paste-input (from `../node_modules/@mattermost/react-native-paste-input`)"
   - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
   - react-native-splash-screen (from `../node_modules/react-native-splash-screen`)
@@ -494,7 +642,6 @@ DEPENDENCIES:
   - RNImageCropPicker (from `../node_modules/react-native-image-crop-picker`)
   - RNInAppBrowser (from `../node_modules/react-native-inappbrowser-reborn`)
   - "RNNotifee (from `../node_modules/@notifee/react-native`)"
-  - RNPermissions (from `../node_modules/react-native-permissions`)
   - RNReactNativeHapticFeedback (from `../node_modules/react-native-haptic-feedback`)
   - RNReanimated (from `../node_modules/react-native-reanimated`)
   - RNScreens (from `../node_modules/react-native-screens`)
@@ -520,6 +667,44 @@ EXTERNAL SOURCES:
     :path: "../node_modules/react-native-linear-gradient"
   DoubleConversion:
     :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec"
+  EXApplication:
+    :path: "../node_modules/expo-application/ios"
+  EXCamera:
+    :path: "../node_modules/expo-camera/ios"
+  EXConstants:
+    :path: "../node_modules/expo-constants/ios"
+  EXFileSystem:
+    :path: "../node_modules/expo-file-system/ios"
+  EXFont:
+    :path: "../node_modules/expo-font/ios"
+  EXImageLoader:
+    :path: "../node_modules/expo-image-loader/ios"
+  EXJSONUtils:
+    :path: "../node_modules/expo-json-utils/ios"
+  EXManifests:
+    :path: "../node_modules/expo-manifests/ios"
+  EXMediaLibrary:
+    :path: "../node_modules/expo-media-library/ios"
+  Expo:
+    :path: "../node_modules/expo"
+  expo-dev-client:
+    :path: "../node_modules/expo-dev-client/ios"
+  expo-dev-launcher:
+    :path: "../node_modules/expo-dev-launcher"
+  expo-dev-menu:
+    :path: "../node_modules/expo-dev-menu"
+  expo-dev-menu-interface:
+    :path: "../node_modules/expo-dev-menu-interface/ios"
+  ExpoImagePicker:
+    :path: "../node_modules/expo-image-picker/ios"
+  ExpoKeepAwake:
+    :path: "../node_modules/expo-keep-awake/ios"
+  ExpoModulesCore:
+    :path: "../node_modules/expo-modules-core"
+  EXSplashScreen:
+    :path: "../node_modules/expo-splash-screen/ios"
+  EXUpdatesInterface:
+    :path: "../node_modules/expo-updates-interface/ios"
   FBLazyVector:
     :path: "../node_modules/react-native/Libraries/FBLazyVector"
   FBReactNativeSpec:
@@ -528,10 +713,6 @@ EXTERNAL SOURCES:
     :podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec"
   hermes-engine:
     :podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec"
-  Permission-Camera:
-    :path: "../node_modules/react-native-permissions/ios/Camera"
-  Permission-PhotoLibrary:
-    :path: "../node_modules/react-native-permissions/ios/PhotoLibrary"
   RCT-Folly:
     :podspec: "../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec"
   RCTRequired:
@@ -566,8 +747,6 @@ EXTERNAL SOURCES:
     :path: "../node_modules/@react-native-camera-roll/camera-roll"
   react-native-image-resizer:
     :path: "../node_modules/@bam.tech/react-native-image-resizer"
-  react-native-pager-view:
-    :path: "../node_modules/react-native-pager-view"
   react-native-paste-input:
     :path: "../node_modules/@mattermost/react-native-paste-input"
   react-native-safe-area-context:
@@ -624,8 +803,6 @@ EXTERNAL SOURCES:
     :path: "../node_modules/react-native-inappbrowser-reborn"
   RNNotifee:
     :path: "../node_modules/@notifee/react-native"
-  RNPermissions:
-    :path: "../node_modules/react-native-permissions"
   RNReactNativeHapticFeedback:
     :path: "../node_modules/react-native-haptic-feedback"
   RNReanimated:
@@ -645,51 +822,67 @@ SPEC CHECKSUMS:
   boost: 57d2868c099736d80fcd648bf211b4431e51a558
   BVLinearGradient: 34a999fda29036898a09c6a6b728b0b4189e1a44
   DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
-  FBLazyVector: ad72713385db5289b19f1ead07e8e4aa26dcb01d
-  FBReactNativeSpec: df2602c11e33d310433496e28a48b4b2be652a61
+  EXApplication: d8f53a7eee90a870a75656280e8d4b85726ea903
+  EXCamera: a323a5942b5e7fc8349e17d728e91c18840ad561
+  EXConstants: f348da07e21b23d2b085e270d7b74f282df1a7d9
+  EXFileSystem: 844e86ca9b5375486ecc4ef06d3838d5597d895d
+  EXFont: 6ea3800df746be7233208d80fe379b8ed74f4272
+  EXImageLoader: fd053169a8ee932dd83bf1fe5487a50c26d27c2b
+  EXJSONUtils: 48b1e764ac35160e6f54d21ab60d7d9501f3e473
+  EXManifests: 500666d48e8dd7ca5a482c9e729e4a7a6c34081b
+  EXMediaLibrary: 792fe9b828b5bfa2c5a8b629730f175af2938285
+  Expo: 04ba1ddde0be07aff4306ae636a1804810679145
+  expo-dev-client: 7c1ef51516853465f4d448c14ddf365167d20361
+  expo-dev-launcher: 90de99d9e5d1a883d81355ca10e87c2f3c81d46e
+  expo-dev-menu: d4369e74d8d21a0ccdee35f7c732e7118b0fee16
+  expo-dev-menu-interface: 6c82ae323c4b8724dead4763ce3ff24a2108bdb1
+  ExpoImagePicker: 270dea232b3a072d981dd564e2cafc63a864edb1
+  ExpoKeepAwake: 69f5f627670d62318410392d03e0b5db0f85759a
+  ExpoModulesCore: 1667335d4f4c9b7801990930e6f0eea42c916a21
+  EXSplashScreen: cd7fb052dff5ba8311d5c2455ecbebffe1b7a8ca
+  EXUpdatesInterface: dd699d1930e28639dcbd70a402caea98e86364ca
+  FBLazyVector: 60195509584153283780abdac5569feffb8f08cc
+  FBReactNativeSpec: 9c191fb58d06dc05ab5559a5505fc32139e9e4a2
   fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
   glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b
-  hermes-engine: 922ccd744f50d9bfde09e9677bf0f3b562ea5fb9
+  hermes-engine: 38bfe887e456b33b697187570a08de33969f5db7
   libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
   libwebp: f62cb61d0a484ba548448a4bd52aabf150ff6eef
-  Permission-Camera: db22e80aa0858a8b6d65979a97f2f481dd8a0ebd
-  Permission-PhotoLibrary: 7d80161682e08042fd8b0bf934ea97a8495e0e6a
   RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1
-  RCTRequired: fd4d923b964658aa0c4091a32c8b2004c6d9e3a6
-  RCTTypeSafety: c276d85975bde3d8448907235c70bf0da257adfd
-  React: e481a67971af1ce9639c9f746b753dd0e84ca108
-  React-callinvoker: 1051c04a94fa9d243786b86380606bad701a3b31
-  React-Codegen: 14b1e716d361d5ad95e0ce1a338f3fa0733a98b5
-  React-Core: 698fc3baecb80d511d987475a16d036cec6d287f
-  React-CoreModules: 59245305f41ff0adfeac334acc0594dea4585a7c
-  React-cxxreact: 49accd2954b0f532805dbcd1918fa6962f32f247
-  React-hermes: d068733294581a085e95b6024e8d951b005e26d3
-  React-jsi: 122b9bce14f4c6c7cb58f28f87912cfe091885fa
-  React-jsiexecutor: 60cf272aababc5212410e4249d17cea14fc36caa
-  React-jsinspector: ff56004b0c974b688a6548c156d5830ad751ae07
-  React-logger: 60a0b5f8bed667ecf9e24fecca1f30d125de6d75
+  RCTRequired: bec48f07daf7bcdc2655a0cde84e07d24d2a9e2a
+  RCTTypeSafety: 171394eebacf71e1cfad79dbfae7ee8fc16ca80a
+  React: d7433ccb6a8c36e4cbed59a73c0700fc83c3e98a
+  React-callinvoker: 15f165009bd22ae829b2b600e50bcc98076ce4b8
+  React-Codegen: b5910000eaf1e0c2f47d29be6f82f5f1264420d7
+  React-Core: b6f2f78d580a90b83fd7b0d1c6911c799f6eac82
+  React-CoreModules: e0cbc1a4f4f3f60e23c476fef7ab37be363ea8c1
+  React-cxxreact: c87f3f124b2117d00d410b35f16c2257e25e50fa
+  React-hermes: c64ca6bdf16a7069773103c9bedaf30ec90ab38f
+  React-jsi: 39729361645568e238081b3b3180fbad803f25a4
+  React-jsiexecutor: 515b703d23ffadeac7687bc2d12fb08b90f0aaa1
+  React-jsinspector: 9f7c9137605e72ca0343db4cea88006cb94856dd
+  React-logger: 957e5dc96d9dbffc6e0f15e0ee4d2b42829ff207
   react-native-blur: 50c9feabacbc5f49b61337ebc32192c6be7ec3c3
   react-native-cameraroll: cb752fda6d5268f1646b4390bd5be1f27706b9a0
   react-native-image-resizer: 00ceb0e05586c7aadf061eea676957a6c2ec60fa
-  react-native-pager-view: b58cb9e9f42f64e50cab3040815772c1d119a2e2
   react-native-paste-input: 3392800944a47c00dddbff23c31c281482209679
   react-native-safe-area-context: 39c2d8be3328df5d437ac1700f4f3a4f75716acc
   react-native-splash-screen: 4312f786b13a81b5169ef346d76d33bc0c6dc457
   react-native-version-number: b415bbec6a13f2df62bf978e85bc0d699462f37f
-  react-native-webview: 9f111dfbcfc826084d6c507f569e5e03342ee1c1
-  React-perflogger: ec8eef2a8f03ecfa6361c2c5fb9197ef4a29cc85
-  React-RCTActionSheet: a0c023b86cf4c862fa9c4eb0f6f91fbe878fb2de
-  React-RCTAnimation: 168d53718c74153947c0109f55900faa64d79439
-  React-RCTAppDelegate: a8efbab128b34aa07a9491c85a41401210b1bec5
-  React-RCTBlob: 9bcbfc893bfda9f6b2eb016329d38c0f6366d31a
-  React-RCTImage: 3fcd4570b4b0f1ac2f4b4b6308dba33ce66c5b50
-  React-RCTLinking: 1edb8e1bb3fc39bf9e13c63d6aaaa3f0c3d18683
-  React-RCTNetwork: 500a79e0e0f67678077df727fabba87a55c043e1
-  React-RCTSettings: cc4414eb84ad756d619076c3999fecbf12896d6f
-  React-RCTText: 2a34261f3da6e34f47a62154def657546ebfa5e1
-  React-RCTVibration: 49d531ec8498e0afa2c9b22c2205784372e3d4f3
-  React-runtimeexecutor: 311feb67600774723fe10eb8801d3138cae9ad67
-  ReactCommon: 03be76588338a27a88d103b35c3c44a3fd43d136
+  react-native-webview: 994b9f8fbb504d6314dc40d83f94f27c6831b3bf
+  React-perflogger: af8a3d31546077f42d729b949925cc4549f14def
+  React-RCTActionSheet: 57cc5adfefbaaf0aae2cf7e10bccd746f2903673
+  React-RCTAnimation: 11c61e94da700c4dc915cf134513764d87fc5e2b
+  React-RCTAppDelegate: c3980adeaadcfd6cb495532e928b36ac6db3c14a
+  React-RCTBlob: ccc5049d742b41971141415ca86b83b201495695
+  React-RCTImage: 7a9226b0944f1e76e8e01e35a9245c2477cdbabb
+  React-RCTLinking: bbe8cc582046a9c04f79c235b73c93700263e8b4
+  React-RCTNetwork: fc2ca322159dc54e06508d4f5c3e934da63dc013
+  React-RCTSettings: f1e9db2cdf946426d3f2b210e4ff4ce0f0d842ef
+  React-RCTText: 1c41dd57e5d742b1396b4eeb251851ce7ff0fca1
+  React-RCTVibration: 5199a180d04873366a83855de55ac33ce60fe4d5
+  React-runtimeexecutor: 7bf0dafc7b727d93c8cb94eb00a9d3753c446c3e
+  ReactCommon: 6f65ea5b7d84deb9e386f670dd11ce499ded7b40
   rn-fetch-blob: f065bb7ab7fb48dd002629f8bdcb0336602d3cba
   RNBackgroundFetch: 8e16176ff415daac743a6eb57afc8e9e14dbe623
   RNCAsyncStorage: 8616bd5a58af409453ea4e1b246521bb76578d60
@@ -700,19 +893,18 @@ SPEC CHECKSUMS:
   RNImageCropPicker: 648356d68fbf9911a1016b3e3723885d28373eda
   RNInAppBrowser: e36d6935517101ccba0e875bac8ad7b0cb655364
   RNNotifee: 053c0ace9c73634709a0214fd9c436a5777a562f
-  RNPermissions: 2fbbcb7244357507f958d626d58eb15fb0013d85
   RNReactNativeHapticFeedback: 1e3efeca9628ff9876ee7cdd9edec1b336913f8c
   RNReanimated: cc5e3aa479cb9170bcccf8204291a6950a3be128
   RNScreens: 218801c16a2782546d30bd2026bb625c0302d70f
-  RNSVG: d7d7bc8229af3842c9cfc3a723c815a52cdd1105
+  RNSVG: c1e76b81c76cdcd34b4e1188852892dc280eb902
   SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d
   SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d
-  segment-analytics-react-native: bd1f13ea95bad2313a9c7130da032af0e9a6da60
+  segment-analytics-react-native: f962dff3a084655a29f9403b8c139c75a3362524
   sovran-react-native: fd3dc8f1a4b14acdc4ad25fc6b4ac4f52a2a2a15
   Swime: d7b2c277503b6cea317774aedc2dce05613f8b0b
   TOCropViewController: edfd4f25713d56905ad1e0b9f5be3fbe0f59c863
-  Yoga: 921eb014669cf9c718ada68b08d362517d564e0c
+  Yoga: 5ed1699acbba8863755998a4245daa200ff3817b
 
-PODFILE CHECKSUM: 95c7fde1130d862b561348cca2b3fb7f9bd84bfb
+PODFILE CHECKSUM: 5570c7b7d6ce7895f95d9db8a3a99b136a3f42c4
 
 COCOAPODS: 1.11.3
diff --git a/ios/Podfile.properties.json b/ios/Podfile.properties.json
new file mode 100644
index 000000000..cb338dda8
--- /dev/null
+++ b/ios/Podfile.properties.json
@@ -0,0 +1,3 @@
+{
+  "expo.jsEngine": "hermes"
+}
\ No newline at end of file
diff --git a/ios/app.xcodeproj/project.pbxproj b/ios/app.xcodeproj/project.pbxproj
deleted file mode 100644
index c82cf898d..000000000
--- a/ios/app.xcodeproj/project.pbxproj
+++ /dev/null
@@ -1,717 +0,0 @@
-// !$*UTF8*$!
-{
-	archiveVersion = 1;
-	classes = {
-	};
-	objectVersion = 54;
-	objects = {
-
-/* Begin PBXBuildFile section */
-		00E356F31AD99517003FC87E /* appTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* appTests.m */; };
-		13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; };
-		13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
-		13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
-		5698CA584FD738B2091BD18F /* libPods-app-appTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C0C7BDE7769B84011D3747DC /* libPods-app-appTests.a */; };
-		67BF1AE6AABFC881715B2D6A /* libPods-app.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8789F612EDA2C48C6064ADD6 /* libPods-app.a */; };
-		E46FE2C12981C0DB007C107C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E46FE2C02981C0DB007C107C /* LaunchScreen.storyboard */; };
-		E4BBD590292C1F5200296224 /* app.entitlements in Resources */ = {isa = PBXBuildFile; fileRef = E4437C9E28581FA7006DA9E7 /* app.entitlements */; };
-/* End PBXBuildFile section */
-
-/* Begin PBXContainerItemProxy section */
-		00E356F41AD99517003FC87E /* PBXContainerItemProxy */ = {
-			isa = PBXContainerItemProxy;
-			containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
-			proxyType = 1;
-			remoteGlobalIDString = 13B07F861A680F5B00A75B9A;
-			remoteInfo = app;
-		};
-/* End PBXContainerItemProxy section */
-
-/* Begin PBXFileReference section */
-		00E356EE1AD99517003FC87E /* appTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = appTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
-		00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
-		00E356F21AD99517003FC87E /* appTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = appTests.m; sourceTree = "<group>"; };
-		13B07F961A680F5B00A75B9A /* app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = app.app; sourceTree = BUILT_PRODUCTS_DIR; };
-		13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = app/AppDelegate.h; sourceTree = "<group>"; };
-		13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = app/AppDelegate.mm; sourceTree = "<group>"; };
-		13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = app/Images.xcassets; sourceTree = "<group>"; };
-		13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = app/Info.plist; sourceTree = "<group>"; };
-		13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = app/main.m; sourceTree = "<group>"; };
-		53DBA218C184B95B107AC33E /* Pods-app.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-app.release.xcconfig"; path = "Target Support Files/Pods-app/Pods-app.release.xcconfig"; sourceTree = "<group>"; };
-		8789F612EDA2C48C6064ADD6 /* libPods-app.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-app.a"; sourceTree = BUILT_PRODUCTS_DIR; };
-		8BB4EDB104E125B8A1913E74 /* Pods-app.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-app.debug.xcconfig"; path = "Target Support Files/Pods-app/Pods-app.debug.xcconfig"; sourceTree = "<group>"; };
-		A8E093A0B5DA947150924A68 /* Pods-app-appTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-app-appTests.release.xcconfig"; path = "Target Support Files/Pods-app-appTests/Pods-app-appTests.release.xcconfig"; sourceTree = "<group>"; };
-		C01FB6762BC17DADC0319338 /* Pods-app-appTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-app-appTests.debug.xcconfig"; path = "Target Support Files/Pods-app-appTests/Pods-app-appTests.debug.xcconfig"; sourceTree = "<group>"; };
-		C0C7BDE7769B84011D3747DC /* libPods-app-appTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-app-appTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
-		E4437C9E28581FA7006DA9E7 /* app.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = app.entitlements; path = app/app.entitlements; sourceTree = "<group>"; };
-		E46FE2C02981C0DB007C107C /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
-		ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
-/* End PBXFileReference section */
-
-/* Begin PBXFrameworksBuildPhase section */
-		00E356EB1AD99517003FC87E /* Frameworks */ = {
-			isa = PBXFrameworksBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-				5698CA584FD738B2091BD18F /* libPods-app-appTests.a in Frameworks */,
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-		};
-		13B07F8C1A680F5B00A75B9A /* Frameworks */ = {
-			isa = PBXFrameworksBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-				67BF1AE6AABFC881715B2D6A /* libPods-app.a in Frameworks */,
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-		};
-/* End PBXFrameworksBuildPhase section */
-
-/* Begin PBXGroup section */
-		00E356EF1AD99517003FC87E /* appTests */ = {
-			isa = PBXGroup;
-			children = (
-				00E356F21AD99517003FC87E /* appTests.m */,
-				00E356F01AD99517003FC87E /* Supporting Files */,
-			);
-			path = appTests;
-			sourceTree = "<group>";
-		};
-		00E356F01AD99517003FC87E /* Supporting Files */ = {
-			isa = PBXGroup;
-			children = (
-				00E356F11AD99517003FC87E /* Info.plist */,
-			);
-			name = "Supporting Files";
-			sourceTree = "<group>";
-		};
-		13B07FAE1A68108700A75B9A /* app */ = {
-			isa = PBXGroup;
-			children = (
-				E4437C9E28581FA7006DA9E7 /* app.entitlements */,
-				13B07FAF1A68108700A75B9A /* AppDelegate.h */,
-				13B07FB01A68108700A75B9A /* AppDelegate.mm */,
-				13B07FB51A68108700A75B9A /* Images.xcassets */,
-				13B07FB61A68108700A75B9A /* Info.plist */,
-				13B07FB71A68108700A75B9A /* main.m */,
-				E46FE2C02981C0DB007C107C /* LaunchScreen.storyboard */,
-			);
-			name = app;
-			sourceTree = "<group>";
-		};
-		2D16E6871FA4F8E400B85C8A /* Frameworks */ = {
-			isa = PBXGroup;
-			children = (
-				ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
-				8789F612EDA2C48C6064ADD6 /* libPods-app.a */,
-				C0C7BDE7769B84011D3747DC /* libPods-app-appTests.a */,
-			);
-			name = Frameworks;
-			sourceTree = "<group>";
-		};
-		832341AE1AAA6A7D00B99B32 /* Libraries */ = {
-			isa = PBXGroup;
-			children = (
-			);
-			name = Libraries;
-			sourceTree = "<group>";
-		};
-		83CBB9F61A601CBA00E9B192 = {
-			isa = PBXGroup;
-			children = (
-				13B07FAE1A68108700A75B9A /* app */,
-				832341AE1AAA6A7D00B99B32 /* Libraries */,
-				00E356EF1AD99517003FC87E /* appTests */,
-				83CBBA001A601CBA00E9B192 /* Products */,
-				2D16E6871FA4F8E400B85C8A /* Frameworks */,
-				BBD78D7AC51CEA395F1C20DB /* Pods */,
-			);
-			indentWidth = 2;
-			sourceTree = "<group>";
-			tabWidth = 2;
-			usesTabs = 0;
-		};
-		83CBBA001A601CBA00E9B192 /* Products */ = {
-			isa = PBXGroup;
-			children = (
-				13B07F961A680F5B00A75B9A /* app.app */,
-				00E356EE1AD99517003FC87E /* appTests.xctest */,
-			);
-			name = Products;
-			sourceTree = "<group>";
-		};
-		BBD78D7AC51CEA395F1C20DB /* Pods */ = {
-			isa = PBXGroup;
-			children = (
-				8BB4EDB104E125B8A1913E74 /* Pods-app.debug.xcconfig */,
-				53DBA218C184B95B107AC33E /* Pods-app.release.xcconfig */,
-				C01FB6762BC17DADC0319338 /* Pods-app-appTests.debug.xcconfig */,
-				A8E093A0B5DA947150924A68 /* Pods-app-appTests.release.xcconfig */,
-			);
-			path = Pods;
-			sourceTree = "<group>";
-		};
-/* End PBXGroup section */
-
-/* Begin PBXNativeTarget section */
-		00E356ED1AD99517003FC87E /* appTests */ = {
-			isa = PBXNativeTarget;
-			buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "appTests" */;
-			buildPhases = (
-				A6849EFFDCB214D4F4EAE972 /* [CP] Check Pods Manifest.lock */,
-				00E356EA1AD99517003FC87E /* Sources */,
-				00E356EB1AD99517003FC87E /* Frameworks */,
-				00E356EC1AD99517003FC87E /* Resources */,
-				DFA0C2B14E2F369C4B2337CE /* [CP] Copy Pods Resources */,
-				B73FFC16945F7B215A06B698 /* [CP] Embed Pods Frameworks */,
-			);
-			buildRules = (
-			);
-			dependencies = (
-				00E356F51AD99517003FC87E /* PBXTargetDependency */,
-			);
-			name = appTests;
-			productName = appTests;
-			productReference = 00E356EE1AD99517003FC87E /* appTests.xctest */;
-			productType = "com.apple.product-type.bundle.unit-test";
-		};
-		13B07F861A680F5B00A75B9A /* app */ = {
-			isa = PBXNativeTarget;
-			buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "app" */;
-			buildPhases = (
-				DDF15D430A078CE70E577FDA /* [CP] Check Pods Manifest.lock */,
-				FD10A7F022414F080027D42C /* Start Packager */,
-				13B07F871A680F5B00A75B9A /* Sources */,
-				13B07F8C1A680F5B00A75B9A /* Frameworks */,
-				13B07F8E1A680F5B00A75B9A /* Resources */,
-				00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
-				45E56D79C207C80AF89FBAA2 /* [CP] Copy Pods Resources */,
-				B0229D18A3C8908C642F9131 /* [CP] Embed Pods Frameworks */,
-			);
-			buildRules = (
-			);
-			dependencies = (
-			);
-			name = app;
-			productName = app;
-			productReference = 13B07F961A680F5B00A75B9A /* app.app */;
-			productType = "com.apple.product-type.application";
-		};
-/* End PBXNativeTarget section */
-
-/* Begin PBXProject section */
-		83CBB9F71A601CBA00E9B192 /* Project object */ = {
-			isa = PBXProject;
-			attributes = {
-				LastUpgradeCheck = 1340;
-				TargetAttributes = {
-					00E356ED1AD99517003FC87E = {
-						CreatedOnToolsVersion = 6.2;
-						TestTargetID = 13B07F861A680F5B00A75B9A;
-					};
-					13B07F861A680F5B00A75B9A = {
-						LastSwiftMigration = 1120;
-					};
-				};
-			};
-			buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "app" */;
-			compatibilityVersion = "Xcode 12.0";
-			developmentRegion = en;
-			hasScannedForEncodings = 0;
-			knownRegions = (
-				en,
-				Base,
-			);
-			mainGroup = 83CBB9F61A601CBA00E9B192;
-			productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
-			projectDirPath = "";
-			projectRoot = "";
-			targets = (
-				13B07F861A680F5B00A75B9A /* app */,
-				00E356ED1AD99517003FC87E /* appTests */,
-			);
-		};
-/* End PBXProject section */
-
-/* Begin PBXResourcesBuildPhase section */
-		00E356EC1AD99517003FC87E /* Resources */ = {
-			isa = PBXResourcesBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-		};
-		13B07F8E1A680F5B00A75B9A /* Resources */ = {
-			isa = PBXResourcesBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-				E46FE2C12981C0DB007C107C /* LaunchScreen.storyboard in Resources */,
-				E4BBD590292C1F5200296224 /* app.entitlements in Resources */,
-				13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-		};
-/* End PBXResourcesBuildPhase section */
-
-/* Begin PBXShellScriptBuildPhase section */
-		00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			inputPaths = (
-				"$(SRCROOT)/.xcode.env.local",
-				"$(SRCROOT)/.xcode.env",
-			);
-			name = "Bundle React Native code and images";
-			outputPaths = (
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = "set -e\n\nWITH_ENVIRONMENT=\"../node_modules/react-native/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"../node_modules/react-native/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n";
-		};
-		45E56D79C207C80AF89FBAA2 /* [CP] Copy Pods Resources */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			inputFileListPaths = (
-				"${PODS_ROOT}/Target Support Files/Pods-app/Pods-app-resources-${CONFIGURATION}-input-files.xcfilelist",
-			);
-			name = "[CP] Copy Pods Resources";
-			outputFileListPaths = (
-				"${PODS_ROOT}/Target Support Files/Pods-app/Pods-app-resources-${CONFIGURATION}-output-files.xcfilelist",
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-app/Pods-app-resources.sh\"\n";
-			showEnvVarsInLog = 0;
-		};
-		A6849EFFDCB214D4F4EAE972 /* [CP] Check Pods Manifest.lock */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			inputFileListPaths = (
-			);
-			inputPaths = (
-				"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
-				"${PODS_ROOT}/Manifest.lock",
-			);
-			name = "[CP] Check Pods Manifest.lock";
-			outputFileListPaths = (
-			);
-			outputPaths = (
-				"$(DERIVED_FILE_DIR)/Pods-app-appTests-checkManifestLockResult.txt",
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
-			showEnvVarsInLog = 0;
-		};
-		B0229D18A3C8908C642F9131 /* [CP] Embed Pods Frameworks */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			inputFileListPaths = (
-				"${PODS_ROOT}/Target Support Files/Pods-app/Pods-app-frameworks-${CONFIGURATION}-input-files.xcfilelist",
-			);
-			name = "[CP] Embed Pods Frameworks";
-			outputFileListPaths = (
-				"${PODS_ROOT}/Target Support Files/Pods-app/Pods-app-frameworks-${CONFIGURATION}-output-files.xcfilelist",
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-app/Pods-app-frameworks.sh\"\n";
-			showEnvVarsInLog = 0;
-		};
-		B73FFC16945F7B215A06B698 /* [CP] Embed Pods Frameworks */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			inputFileListPaths = (
-				"${PODS_ROOT}/Target Support Files/Pods-app-appTests/Pods-app-appTests-frameworks-${CONFIGURATION}-input-files.xcfilelist",
-			);
-			name = "[CP] Embed Pods Frameworks";
-			outputFileListPaths = (
-				"${PODS_ROOT}/Target Support Files/Pods-app-appTests/Pods-app-appTests-frameworks-${CONFIGURATION}-output-files.xcfilelist",
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-app-appTests/Pods-app-appTests-frameworks.sh\"\n";
-			showEnvVarsInLog = 0;
-		};
-		DDF15D430A078CE70E577FDA /* [CP] Check Pods Manifest.lock */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			inputFileListPaths = (
-			);
-			inputPaths = (
-				"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
-				"${PODS_ROOT}/Manifest.lock",
-			);
-			name = "[CP] Check Pods Manifest.lock";
-			outputFileListPaths = (
-			);
-			outputPaths = (
-				"$(DERIVED_FILE_DIR)/Pods-app-checkManifestLockResult.txt",
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
-			showEnvVarsInLog = 0;
-		};
-		DFA0C2B14E2F369C4B2337CE /* [CP] Copy Pods Resources */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			inputFileListPaths = (
-				"${PODS_ROOT}/Target Support Files/Pods-app-appTests/Pods-app-appTests-resources-${CONFIGURATION}-input-files.xcfilelist",
-			);
-			name = "[CP] Copy Pods Resources";
-			outputFileListPaths = (
-				"${PODS_ROOT}/Target Support Files/Pods-app-appTests/Pods-app-appTests-resources-${CONFIGURATION}-output-files.xcfilelist",
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-app-appTests/Pods-app-appTests-resources.sh\"\n";
-			showEnvVarsInLog = 0;
-		};
-		FD10A7F022414F080027D42C /* Start Packager */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			inputFileListPaths = (
-			);
-			inputPaths = (
-			);
-			name = "Start Packager";
-			outputFileListPaths = (
-			);
-			outputPaths = (
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = "export RCT_METRO_PORT=\"${RCT_METRO_PORT:=8081}\"\necho \"export RCT_METRO_PORT=${RCT_METRO_PORT}\" > \"${SRCROOT}/../node_modules/react-native/scripts/.packager.env\"\nif [ -z \"${RCT_NO_LAUNCH_PACKAGER+xxx}\" ] ; then\n  if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then\n    if ! curl -s \"http://localhost:${RCT_METRO_PORT}/status\" | grep -q \"packager-status:running\" ; then\n      echo \"Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly\"\n      exit 2\n    fi\n  else\n    open \"$SRCROOT/../node_modules/react-native/scripts/launchPackager.command\" || echo \"Can't start packager automatically\"\n  fi\nfi\n";
-			showEnvVarsInLog = 0;
-		};
-/* End PBXShellScriptBuildPhase section */
-
-/* Begin PBXSourcesBuildPhase section */
-		00E356EA1AD99517003FC87E /* Sources */ = {
-			isa = PBXSourcesBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-				00E356F31AD99517003FC87E /* appTests.m in Sources */,
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-		};
-		13B07F871A680F5B00A75B9A /* Sources */ = {
-			isa = PBXSourcesBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-				13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */,
-				13B07FC11A68108700A75B9A /* main.m in Sources */,
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-		};
-/* End PBXSourcesBuildPhase section */
-
-/* Begin PBXTargetDependency section */
-		00E356F51AD99517003FC87E /* PBXTargetDependency */ = {
-			isa = PBXTargetDependency;
-			target = 13B07F861A680F5B00A75B9A /* app */;
-			targetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */;
-		};
-/* End PBXTargetDependency section */
-
-/* Begin XCBuildConfiguration section */
-		00E356F61AD99517003FC87E /* Debug */ = {
-			isa = XCBuildConfiguration;
-			baseConfigurationReference = C01FB6762BC17DADC0319338 /* Pods-app-appTests.debug.xcconfig */;
-			buildSettings = {
-				ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
-				BUNDLE_LOADER = "$(TEST_HOST)";
-				GCC_PREPROCESSOR_DEFINITIONS = (
-					"DEBUG=1",
-					"$(inherited)",
-				);
-				INFOPLIST_FILE = appTests/Info.plist;
-				IPHONEOS_DEPLOYMENT_TARGET = 12.0;
-				LD_RUNPATH_SEARCH_PATHS = (
-					"$(inherited)",
-					"@executable_path/Frameworks",
-					"@loader_path/Frameworks",
-				);
-				OTHER_LDFLAGS = (
-					"-ObjC",
-					"-lc++",
-					"$(inherited)",
-				);
-				PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
-				PRODUCT_NAME = "$(TARGET_NAME)";
-				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/app.app/app";
-			};
-			name = Debug;
-		};
-		00E356F71AD99517003FC87E /* Release */ = {
-			isa = XCBuildConfiguration;
-			baseConfigurationReference = A8E093A0B5DA947150924A68 /* Pods-app-appTests.release.xcconfig */;
-			buildSettings = {
-				ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
-				BUNDLE_LOADER = "$(TEST_HOST)";
-				COPY_PHASE_STRIP = NO;
-				INFOPLIST_FILE = appTests/Info.plist;
-				IPHONEOS_DEPLOYMENT_TARGET = 12.0;
-				LD_RUNPATH_SEARCH_PATHS = (
-					"$(inherited)",
-					"@executable_path/Frameworks",
-					"@loader_path/Frameworks",
-				);
-				OTHER_LDFLAGS = (
-					"-ObjC",
-					"-lc++",
-					"$(inherited)",
-				);
-				PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
-				PRODUCT_NAME = "$(TARGET_NAME)";
-				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/app.app/app";
-			};
-			name = Release;
-		};
-		13B07F941A680F5B00A75B9A /* Debug */ = {
-			isa = XCBuildConfiguration;
-			baseConfigurationReference = 8BB4EDB104E125B8A1913E74 /* Pods-app.debug.xcconfig */;
-			buildSettings = {
-				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
-				CLANG_ENABLE_MODULES = YES;
-				CODE_SIGN_ENTITLEMENTS = app/app.entitlements;
-				CURRENT_PROJECT_VERSION = 1;
-				DEVELOPMENT_TEAM = B3LX46C5HS;
-				ENABLE_BITCODE = NO;
-				"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
-				INFOPLIST_FILE = app/Info.plist;
-				IPHONEOS_DEPLOYMENT_TARGET = 12.4;
-				LD_RUNPATH_SEARCH_PATHS = (
-					"$(inherited)",
-					"@executable_path/Frameworks",
-				);
-				OTHER_LDFLAGS = (
-					"$(inherited)",
-					"-ObjC",
-					"-lc++",
-				);
-				PRODUCT_BUNDLE_IDENTIFIER = xyz.blueskyweb.app;
-				PRODUCT_NAME = app;
-				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
-				SWIFT_VERSION = 5.0;
-				VERSIONING_SYSTEM = "apple-generic";
-			};
-			name = Debug;
-		};
-		13B07F951A680F5B00A75B9A /* Release */ = {
-			isa = XCBuildConfiguration;
-			baseConfigurationReference = 53DBA218C184B95B107AC33E /* Pods-app.release.xcconfig */;
-			buildSettings = {
-				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
-				CLANG_ENABLE_MODULES = YES;
-				CODE_SIGN_ENTITLEMENTS = app/app.entitlements;
-				CURRENT_PROJECT_VERSION = 1;
-				DEVELOPMENT_TEAM = B3LX46C5HS;
-				"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
-				INFOPLIST_FILE = app/Info.plist;
-				IPHONEOS_DEPLOYMENT_TARGET = 12.4;
-				LD_RUNPATH_SEARCH_PATHS = (
-					"$(inherited)",
-					"@executable_path/Frameworks",
-				);
-				MARKETING_VERSION = 1.0;
-				OTHER_LDFLAGS = (
-					"$(inherited)",
-					"-ObjC",
-					"-lc++",
-				);
-				PRODUCT_BUNDLE_IDENTIFIER = xyz.blueskyweb.app;
-				PRODUCT_NAME = app;
-				SWIFT_VERSION = 5.0;
-				VERSIONING_SYSTEM = "apple-generic";
-			};
-			name = Release;
-		};
-		83CBBA201A601CBA00E9B192 /* Debug */ = {
-			isa = XCBuildConfiguration;
-			buildSettings = {
-				ALWAYS_SEARCH_USER_PATHS = NO;
-				CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
-				CLANG_CXX_LANGUAGE_STANDARD = "c++17";
-				CLANG_CXX_LIBRARY = "libc++";
-				CLANG_ENABLE_MODULES = YES;
-				CLANG_ENABLE_OBJC_ARC = YES;
-				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
-				CLANG_WARN_BOOL_CONVERSION = YES;
-				CLANG_WARN_COMMA = YES;
-				CLANG_WARN_CONSTANT_CONVERSION = YES;
-				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
-				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
-				CLANG_WARN_EMPTY_BODY = YES;
-				CLANG_WARN_ENUM_CONVERSION = YES;
-				CLANG_WARN_INFINITE_RECURSION = YES;
-				CLANG_WARN_INT_CONVERSION = YES;
-				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
-				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
-				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
-				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
-				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
-				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
-				CLANG_WARN_STRICT_PROTOTYPES = YES;
-				CLANG_WARN_SUSPICIOUS_MOVE = YES;
-				CLANG_WARN_UNREACHABLE_CODE = YES;
-				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
-				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
-				COPY_PHASE_STRIP = NO;
-				ENABLE_STRICT_OBJC_MSGSEND = YES;
-				ENABLE_TESTABILITY = YES;
-				"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386;
-				GCC_C_LANGUAGE_STANDARD = gnu99;
-				GCC_DYNAMIC_NO_PIC = NO;
-				GCC_NO_COMMON_BLOCKS = YES;
-				GCC_OPTIMIZATION_LEVEL = 0;
-				GCC_PREPROCESSOR_DEFINITIONS = (
-					"DEBUG=1",
-					"$(inherited)",
-				);
-				GCC_SYMBOLS_PRIVATE_EXTERN = NO;
-				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
-				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
-				GCC_WARN_UNDECLARED_SELECTOR = YES;
-				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
-				GCC_WARN_UNUSED_FUNCTION = YES;
-				GCC_WARN_UNUSED_VARIABLE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 12.4;
-				LD_RUNPATH_SEARCH_PATHS = (
-					/usr/lib/swift,
-					"$(inherited)",
-				);
-				LIBRARY_SEARCH_PATHS = (
-					"\"$(SDKROOT)/usr/lib/swift\"",
-					"\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
-					"\"$(inherited)\"",
-				);
-				MTL_ENABLE_DEBUG_INFO = YES;
-				ONLY_ACTIVE_ARCH = YES;
-				OTHER_CPLUSPLUSFLAGS = (
-					"$(OTHER_CFLAGS)",
-					"-DFOLLY_NO_CONFIG",
-					"-DFOLLY_MOBILE=1",
-					"-DFOLLY_USE_LIBCPP=1",
-				);
-				REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
-				SDKROOT = iphoneos;
-			};
-			name = Debug;
-		};
-		83CBBA211A601CBA00E9B192 /* Release */ = {
-			isa = XCBuildConfiguration;
-			buildSettings = {
-				ALWAYS_SEARCH_USER_PATHS = NO;
-				CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
-				CLANG_CXX_LANGUAGE_STANDARD = "c++17";
-				CLANG_CXX_LIBRARY = "libc++";
-				CLANG_ENABLE_MODULES = YES;
-				CLANG_ENABLE_OBJC_ARC = YES;
-				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
-				CLANG_WARN_BOOL_CONVERSION = YES;
-				CLANG_WARN_COMMA = YES;
-				CLANG_WARN_CONSTANT_CONVERSION = YES;
-				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
-				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
-				CLANG_WARN_EMPTY_BODY = YES;
-				CLANG_WARN_ENUM_CONVERSION = YES;
-				CLANG_WARN_INFINITE_RECURSION = YES;
-				CLANG_WARN_INT_CONVERSION = YES;
-				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
-				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
-				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
-				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
-				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
-				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
-				CLANG_WARN_STRICT_PROTOTYPES = YES;
-				CLANG_WARN_SUSPICIOUS_MOVE = YES;
-				CLANG_WARN_UNREACHABLE_CODE = YES;
-				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
-				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
-				COPY_PHASE_STRIP = YES;
-				ENABLE_NS_ASSERTIONS = NO;
-				ENABLE_STRICT_OBJC_MSGSEND = YES;
-				"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386;
-				GCC_C_LANGUAGE_STANDARD = gnu99;
-				GCC_NO_COMMON_BLOCKS = YES;
-				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
-				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
-				GCC_WARN_UNDECLARED_SELECTOR = YES;
-				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
-				GCC_WARN_UNUSED_FUNCTION = YES;
-				GCC_WARN_UNUSED_VARIABLE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 12.4;
-				LD_RUNPATH_SEARCH_PATHS = (
-					/usr/lib/swift,
-					"$(inherited)",
-				);
-				LIBRARY_SEARCH_PATHS = (
-					"\"$(SDKROOT)/usr/lib/swift\"",
-					"\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
-					"\"$(inherited)\"",
-				);
-				MTL_ENABLE_DEBUG_INFO = NO;
-				OTHER_CPLUSPLUSFLAGS = (
-					"$(OTHER_CFLAGS)",
-					"-DFOLLY_NO_CONFIG",
-					"-DFOLLY_MOBILE=1",
-					"-DFOLLY_USE_LIBCPP=1",
-				);
-				REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
-				SDKROOT = iphoneos;
-				VALIDATE_PRODUCT = YES;
-			};
-			name = Release;
-		};
-/* End XCBuildConfiguration section */
-
-/* Begin XCConfigurationList section */
-		00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "appTests" */ = {
-			isa = XCConfigurationList;
-			buildConfigurations = (
-				00E356F61AD99517003FC87E /* Debug */,
-				00E356F71AD99517003FC87E /* Release */,
-			);
-			defaultConfigurationIsVisible = 0;
-			defaultConfigurationName = Release;
-		};
-		13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "app" */ = {
-			isa = XCConfigurationList;
-			buildConfigurations = (
-				13B07F941A680F5B00A75B9A /* Debug */,
-				13B07F951A680F5B00A75B9A /* Release */,
-			);
-			defaultConfigurationIsVisible = 0;
-			defaultConfigurationName = Release;
-		};
-		83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "app" */ = {
-			isa = XCConfigurationList;
-			buildConfigurations = (
-				83CBBA201A601CBA00E9B192 /* Debug */,
-				83CBBA211A601CBA00E9B192 /* Release */,
-			);
-			defaultConfigurationIsVisible = 0;
-			defaultConfigurationName = Release;
-		};
-/* End XCConfigurationList section */
-	};
-	rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */;
-}
diff --git a/ios/app/AppDelegate.h b/ios/app/AppDelegate.h
deleted file mode 100644
index 5d2808256..000000000
--- a/ios/app/AppDelegate.h
+++ /dev/null
@@ -1,6 +0,0 @@
-#import <RCTAppDelegate.h>
-#import <UIKit/UIKit.h>
-
-@interface AppDelegate : RCTAppDelegate
-
-@end
diff --git a/ios/app/AppDelegate.mm b/ios/app/AppDelegate.mm
deleted file mode 100644
index 09aad895f..000000000
--- a/ios/app/AppDelegate.mm
+++ /dev/null
@@ -1,62 +0,0 @@
-#import "AppDelegate.h"
-
-#import <React/RCTBundleURLProvider.h>
-
-// universal links
-#import <React/RCTLinkingManager.h>
-
-// splash screen
-#import "RNSplashScreen.h"
-
-#import <TSBackgroundFetch/TSBackgroundFetch.h>
-
-@implementation AppDelegate
-
-- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
-{
-  // Show the splash screen
-  // [RNSplashScreen show]; 
-  
-  // Register BackgroundFetch
-  [[TSBackgroundFetch sharedInstance] didFinishLaunching];
-
-  self.moduleName = @"xyz.blueskyweb.app";
-  self.initialProps = @{};
-  return [super application:application didFinishLaunchingWithOptions:launchOptions];
-}
-
-- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
-{
-#if DEBUG
-  return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
-#else
-  return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
-#endif
-}
-
-/// This method controls whether the `concurrentRoot`feature of React18 is turned on or off.
-///
-/// @see: https://reactjs.org/blog/2022/03/29/react-v18.html
-/// @note: This requires to be rendering on Fabric (i.e. on the New Architecture).
-/// @return: `true` if the `concurrentRoot` feature is enabled. Otherwise, it returns `false`.
-- (BOOL)concurrentRootEnabled
-{
-  return true;
-}
-
-// universal links
-- (BOOL)application:(UIApplication *)application
-  openURL:(NSURL *)url
-  options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
-{
-  return [RCTLinkingManager application:application openURL:url options:options];
-}
-
-- (BOOL)application:(UIApplication *)application
-  continueUserActivity:(nonnull NSUserActivity *)userActivity
-  restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler
-{
-  return [RCTLinkingManager application:application continueUserActivity:userActivity restorationHandler:restorationHandler];
-}
-
-@end
diff --git a/ios/app/Images.xcassets/AppIcon.appiconset/100.png b/ios/app/Images.xcassets/AppIcon.appiconset/100.png
deleted file mode 100644
index 2ab2cc191..000000000
--- a/ios/app/Images.xcassets/AppIcon.appiconset/100.png
+++ /dev/null
Binary files differdiff --git a/ios/app/Images.xcassets/AppIcon.appiconset/114.png b/ios/app/Images.xcassets/AppIcon.appiconset/114.png
deleted file mode 100644
index 37ce4787a..000000000
--- a/ios/app/Images.xcassets/AppIcon.appiconset/114.png
+++ /dev/null
Binary files differdiff --git a/ios/app/Images.xcassets/AppIcon.appiconset/120.png b/ios/app/Images.xcassets/AppIcon.appiconset/120.png
deleted file mode 100644
index 5288bd5bd..000000000
--- a/ios/app/Images.xcassets/AppIcon.appiconset/120.png
+++ /dev/null
Binary files differdiff --git a/ios/app/Images.xcassets/AppIcon.appiconset/128.png b/ios/app/Images.xcassets/AppIcon.appiconset/128.png
deleted file mode 100644
index ba401db8f..000000000
--- a/ios/app/Images.xcassets/AppIcon.appiconset/128.png
+++ /dev/null
Binary files differdiff --git a/ios/app/Images.xcassets/AppIcon.appiconset/144.png b/ios/app/Images.xcassets/AppIcon.appiconset/144.png
deleted file mode 100644
index ae573d2e6..000000000
--- a/ios/app/Images.xcassets/AppIcon.appiconset/144.png
+++ /dev/null
Binary files differdiff --git a/ios/app/Images.xcassets/AppIcon.appiconset/152.png b/ios/app/Images.xcassets/AppIcon.appiconset/152.png
deleted file mode 100644
index 255663d59..000000000
--- a/ios/app/Images.xcassets/AppIcon.appiconset/152.png
+++ /dev/null
Binary files differdiff --git a/ios/app/Images.xcassets/AppIcon.appiconset/16.png b/ios/app/Images.xcassets/AppIcon.appiconset/16.png
deleted file mode 100644
index be4e2b0b3..000000000
--- a/ios/app/Images.xcassets/AppIcon.appiconset/16.png
+++ /dev/null
Binary files differdiff --git a/ios/app/Images.xcassets/AppIcon.appiconset/167.png b/ios/app/Images.xcassets/AppIcon.appiconset/167.png
deleted file mode 100644
index 84f4bfa29..000000000
--- a/ios/app/Images.xcassets/AppIcon.appiconset/167.png
+++ /dev/null
Binary files differdiff --git a/ios/app/Images.xcassets/AppIcon.appiconset/172.png b/ios/app/Images.xcassets/AppIcon.appiconset/172.png
deleted file mode 100644
index 3532bb966..000000000
--- a/ios/app/Images.xcassets/AppIcon.appiconset/172.png
+++ /dev/null
Binary files differdiff --git a/ios/app/Images.xcassets/AppIcon.appiconset/180.png b/ios/app/Images.xcassets/AppIcon.appiconset/180.png
deleted file mode 100644
index 114c2a4d7..000000000
--- a/ios/app/Images.xcassets/AppIcon.appiconset/180.png
+++ /dev/null
Binary files differdiff --git a/ios/app/Images.xcassets/AppIcon.appiconset/196.png b/ios/app/Images.xcassets/AppIcon.appiconset/196.png
deleted file mode 100644
index a71f3d298..000000000
--- a/ios/app/Images.xcassets/AppIcon.appiconset/196.png
+++ /dev/null
Binary files differdiff --git a/ios/app/Images.xcassets/AppIcon.appiconset/20.png b/ios/app/Images.xcassets/AppIcon.appiconset/20.png
deleted file mode 100644
index c6df8f017..000000000
--- a/ios/app/Images.xcassets/AppIcon.appiconset/20.png
+++ /dev/null
Binary files differdiff --git a/ios/app/Images.xcassets/AppIcon.appiconset/216.png b/ios/app/Images.xcassets/AppIcon.appiconset/216.png
deleted file mode 100644
index e2839dcc1..000000000
--- a/ios/app/Images.xcassets/AppIcon.appiconset/216.png
+++ /dev/null
Binary files differdiff --git a/ios/app/Images.xcassets/AppIcon.appiconset/256.png b/ios/app/Images.xcassets/AppIcon.appiconset/256.png
deleted file mode 100644
index 16d647a91..000000000
--- a/ios/app/Images.xcassets/AppIcon.appiconset/256.png
+++ /dev/null
Binary files differdiff --git a/ios/app/Images.xcassets/AppIcon.appiconset/29.png b/ios/app/Images.xcassets/AppIcon.appiconset/29.png
deleted file mode 100644
index eb2ffd258..000000000
--- a/ios/app/Images.xcassets/AppIcon.appiconset/29.png
+++ /dev/null
Binary files differdiff --git a/ios/app/Images.xcassets/AppIcon.appiconset/32.png b/ios/app/Images.xcassets/AppIcon.appiconset/32.png
deleted file mode 100644
index ec90d9f23..000000000
--- a/ios/app/Images.xcassets/AppIcon.appiconset/32.png
+++ /dev/null
Binary files differdiff --git a/ios/app/Images.xcassets/AppIcon.appiconset/40.png b/ios/app/Images.xcassets/AppIcon.appiconset/40.png
deleted file mode 100644
index b9565b378..000000000
--- a/ios/app/Images.xcassets/AppIcon.appiconset/40.png
+++ /dev/null
Binary files differdiff --git a/ios/app/Images.xcassets/AppIcon.appiconset/48.png b/ios/app/Images.xcassets/AppIcon.appiconset/48.png
deleted file mode 100644
index d33d0b1f1..000000000
--- a/ios/app/Images.xcassets/AppIcon.appiconset/48.png
+++ /dev/null
Binary files differdiff --git a/ios/app/Images.xcassets/AppIcon.appiconset/50.png b/ios/app/Images.xcassets/AppIcon.appiconset/50.png
deleted file mode 100644
index f6bb30996..000000000
--- a/ios/app/Images.xcassets/AppIcon.appiconset/50.png
+++ /dev/null
Binary files differdiff --git a/ios/app/Images.xcassets/AppIcon.appiconset/512.png b/ios/app/Images.xcassets/AppIcon.appiconset/512.png
deleted file mode 100644
index d732c6d74..000000000
--- a/ios/app/Images.xcassets/AppIcon.appiconset/512.png
+++ /dev/null
Binary files differdiff --git a/ios/app/Images.xcassets/AppIcon.appiconset/55.png b/ios/app/Images.xcassets/AppIcon.appiconset/55.png
deleted file mode 100644
index f81097bde..000000000
--- a/ios/app/Images.xcassets/AppIcon.appiconset/55.png
+++ /dev/null
Binary files differdiff --git a/ios/app/Images.xcassets/AppIcon.appiconset/57.png b/ios/app/Images.xcassets/AppIcon.appiconset/57.png
deleted file mode 100644
index ec8574808..000000000
--- a/ios/app/Images.xcassets/AppIcon.appiconset/57.png
+++ /dev/null
Binary files differdiff --git a/ios/app/Images.xcassets/AppIcon.appiconset/58.png b/ios/app/Images.xcassets/AppIcon.appiconset/58.png
deleted file mode 100644
index 38bf21bf2..000000000
--- a/ios/app/Images.xcassets/AppIcon.appiconset/58.png
+++ /dev/null
Binary files differdiff --git a/ios/app/Images.xcassets/AppIcon.appiconset/60.png b/ios/app/Images.xcassets/AppIcon.appiconset/60.png
deleted file mode 100644
index 51c0afaf7..000000000
--- a/ios/app/Images.xcassets/AppIcon.appiconset/60.png
+++ /dev/null
Binary files differdiff --git a/ios/app/Images.xcassets/AppIcon.appiconset/64.png b/ios/app/Images.xcassets/AppIcon.appiconset/64.png
deleted file mode 100644
index 4aa33f782..000000000
--- a/ios/app/Images.xcassets/AppIcon.appiconset/64.png
+++ /dev/null
Binary files differdiff --git a/ios/app/Images.xcassets/AppIcon.appiconset/66.png b/ios/app/Images.xcassets/AppIcon.appiconset/66.png
deleted file mode 100644
index e2a77b362..000000000
--- a/ios/app/Images.xcassets/AppIcon.appiconset/66.png
+++ /dev/null
Binary files differdiff --git a/ios/app/Images.xcassets/AppIcon.appiconset/72.png b/ios/app/Images.xcassets/AppIcon.appiconset/72.png
deleted file mode 100644
index ec793ba83..000000000
--- a/ios/app/Images.xcassets/AppIcon.appiconset/72.png
+++ /dev/null
Binary files differdiff --git a/ios/app/Images.xcassets/AppIcon.appiconset/76.png b/ios/app/Images.xcassets/AppIcon.appiconset/76.png
deleted file mode 100644
index 1f275d144..000000000
--- a/ios/app/Images.xcassets/AppIcon.appiconset/76.png
+++ /dev/null
Binary files differdiff --git a/ios/app/Images.xcassets/AppIcon.appiconset/80.png b/ios/app/Images.xcassets/AppIcon.appiconset/80.png
deleted file mode 100644
index 9efd70e58..000000000
--- a/ios/app/Images.xcassets/AppIcon.appiconset/80.png
+++ /dev/null
Binary files differdiff --git a/ios/app/Images.xcassets/AppIcon.appiconset/87.png b/ios/app/Images.xcassets/AppIcon.appiconset/87.png
deleted file mode 100644
index 4527df1de..000000000
--- a/ios/app/Images.xcassets/AppIcon.appiconset/87.png
+++ /dev/null
Binary files differdiff --git a/ios/app/Images.xcassets/AppIcon.appiconset/88.png b/ios/app/Images.xcassets/AppIcon.appiconset/88.png
deleted file mode 100644
index 5223e3fad..000000000
--- a/ios/app/Images.xcassets/AppIcon.appiconset/88.png
+++ /dev/null
Binary files differdiff --git a/ios/app/Images.xcassets/AppIcon.appiconset/92.png b/ios/app/Images.xcassets/AppIcon.appiconset/92.png
deleted file mode 100644
index c9d526d7b..000000000
--- a/ios/app/Images.xcassets/AppIcon.appiconset/92.png
+++ /dev/null
Binary files differdiff --git a/ios/app/Images.xcassets/AppIcon.appiconset/Contents.json b/ios/app/Images.xcassets/AppIcon.appiconset/Contents.json
deleted file mode 100644
index 8e70699a7..000000000
--- a/ios/app/Images.xcassets/AppIcon.appiconset/Contents.json
+++ /dev/null
@@ -1,346 +0,0 @@
-{
-  "images" : [
-    {
-      "filename" : "40.png",
-      "idiom" : "iphone",
-      "scale" : "2x",
-      "size" : "20x20"
-    },
-    {
-      "filename" : "60.png",
-      "idiom" : "iphone",
-      "scale" : "3x",
-      "size" : "20x20"
-    },
-    {
-      "filename" : "29.png",
-      "idiom" : "iphone",
-      "scale" : "1x",
-      "size" : "29x29"
-    },
-    {
-      "filename" : "58.png",
-      "idiom" : "iphone",
-      "scale" : "2x",
-      "size" : "29x29"
-    },
-    {
-      "filename" : "87.png",
-      "idiom" : "iphone",
-      "scale" : "3x",
-      "size" : "29x29"
-    },
-    {
-      "filename" : "80.png",
-      "idiom" : "iphone",
-      "scale" : "2x",
-      "size" : "40x40"
-    },
-    {
-      "filename" : "120.png",
-      "idiom" : "iphone",
-      "scale" : "3x",
-      "size" : "40x40"
-    },
-    {
-      "filename" : "57.png",
-      "idiom" : "iphone",
-      "scale" : "1x",
-      "size" : "57x57"
-    },
-    {
-      "filename" : "114.png",
-      "idiom" : "iphone",
-      "scale" : "2x",
-      "size" : "57x57"
-    },
-    {
-      "filename" : "120.png",
-      "idiom" : "iphone",
-      "scale" : "2x",
-      "size" : "60x60"
-    },
-    {
-      "filename" : "180.png",
-      "idiom" : "iphone",
-      "scale" : "3x",
-      "size" : "60x60"
-    },
-    {
-      "filename" : "20.png",
-      "idiom" : "ipad",
-      "scale" : "1x",
-      "size" : "20x20"
-    },
-    {
-      "filename" : "40.png",
-      "idiom" : "ipad",
-      "scale" : "2x",
-      "size" : "20x20"
-    },
-    {
-      "filename" : "29.png",
-      "idiom" : "ipad",
-      "scale" : "1x",
-      "size" : "29x29"
-    },
-    {
-      "filename" : "58.png",
-      "idiom" : "ipad",
-      "scale" : "2x",
-      "size" : "29x29"
-    },
-    {
-      "filename" : "40.png",
-      "idiom" : "ipad",
-      "scale" : "1x",
-      "size" : "40x40"
-    },
-    {
-      "filename" : "80.png",
-      "idiom" : "ipad",
-      "scale" : "2x",
-      "size" : "40x40"
-    },
-    {
-      "filename" : "50.png",
-      "idiom" : "ipad",
-      "scale" : "1x",
-      "size" : "50x50"
-    },
-    {
-      "filename" : "100.png",
-      "idiom" : "ipad",
-      "scale" : "2x",
-      "size" : "50x50"
-    },
-    {
-      "filename" : "72.png",
-      "idiom" : "ipad",
-      "scale" : "1x",
-      "size" : "72x72"
-    },
-    {
-      "filename" : "144.png",
-      "idiom" : "ipad",
-      "scale" : "2x",
-      "size" : "72x72"
-    },
-    {
-      "filename" : "76.png",
-      "idiom" : "ipad",
-      "scale" : "1x",
-      "size" : "76x76"
-    },
-    {
-      "filename" : "152.png",
-      "idiom" : "ipad",
-      "scale" : "2x",
-      "size" : "76x76"
-    },
-    {
-      "filename" : "167.png",
-      "idiom" : "ipad",
-      "scale" : "2x",
-      "size" : "83.5x83.5"
-    },
-    {
-      "filename" : "1024.png",
-      "idiom" : "ios-marketing",
-      "scale" : "1x",
-      "size" : "1024x1024"
-    },
-    {
-      "filename" : "16.png",
-      "idiom" : "mac",
-      "scale" : "1x",
-      "size" : "16x16"
-    },
-    {
-      "filename" : "32.png",
-      "idiom" : "mac",
-      "scale" : "2x",
-      "size" : "16x16"
-    },
-    {
-      "filename" : "32.png",
-      "idiom" : "mac",
-      "scale" : "1x",
-      "size" : "32x32"
-    },
-    {
-      "filename" : "64.png",
-      "idiom" : "mac",
-      "scale" : "2x",
-      "size" : "32x32"
-    },
-    {
-      "filename" : "128.png",
-      "idiom" : "mac",
-      "scale" : "1x",
-      "size" : "128x128"
-    },
-    {
-      "filename" : "256.png",
-      "idiom" : "mac",
-      "scale" : "2x",
-      "size" : "128x128"
-    },
-    {
-      "filename" : "256.png",
-      "idiom" : "mac",
-      "scale" : "1x",
-      "size" : "256x256"
-    },
-    {
-      "filename" : "512.png",
-      "idiom" : "mac",
-      "scale" : "2x",
-      "size" : "256x256"
-    },
-    {
-      "filename" : "512.png",
-      "idiom" : "mac",
-      "scale" : "1x",
-      "size" : "512x512"
-    },
-    {
-      "filename" : "1024.png",
-      "idiom" : "mac",
-      "scale" : "2x",
-      "size" : "512x512"
-    },
-    {
-      "filename" : "48.png",
-      "idiom" : "watch",
-      "role" : "notificationCenter",
-      "scale" : "2x",
-      "size" : "24x24",
-      "subtype" : "38mm"
-    },
-    {
-      "filename" : "55.png",
-      "idiom" : "watch",
-      "role" : "notificationCenter",
-      "scale" : "2x",
-      "size" : "27.5x27.5",
-      "subtype" : "42mm"
-    },
-    {
-      "filename" : "58.png",
-      "idiom" : "watch",
-      "role" : "companionSettings",
-      "scale" : "2x",
-      "size" : "29x29"
-    },
-    {
-      "filename" : "87.png",
-      "idiom" : "watch",
-      "role" : "companionSettings",
-      "scale" : "3x",
-      "size" : "29x29"
-    },
-    {
-      "filename" : "66.png",
-      "idiom" : "watch",
-      "role" : "notificationCenter",
-      "scale" : "2x",
-      "size" : "33x33",
-      "subtype" : "45mm"
-    },
-    {
-      "filename" : "80.png",
-      "idiom" : "watch",
-      "role" : "appLauncher",
-      "scale" : "2x",
-      "size" : "40x40",
-      "subtype" : "38mm"
-    },
-    {
-      "filename" : "88.png",
-      "idiom" : "watch",
-      "role" : "appLauncher",
-      "scale" : "2x",
-      "size" : "44x44",
-      "subtype" : "40mm"
-    },
-    {
-      "filename" : "92.png",
-      "idiom" : "watch",
-      "role" : "appLauncher",
-      "scale" : "2x",
-      "size" : "46x46",
-      "subtype" : "41mm"
-    },
-    {
-      "filename" : "100.png",
-      "idiom" : "watch",
-      "role" : "appLauncher",
-      "scale" : "2x",
-      "size" : "50x50",
-      "subtype" : "44mm"
-    },
-    {
-      "idiom" : "watch",
-      "role" : "appLauncher",
-      "scale" : "2x",
-      "size" : "51x51",
-      "subtype" : "45mm"
-    },
-    {
-      "idiom" : "watch",
-      "role" : "appLauncher",
-      "scale" : "2x",
-      "size" : "54x54",
-      "subtype" : "49mm"
-    },
-    {
-      "filename" : "172.png",
-      "idiom" : "watch",
-      "role" : "quickLook",
-      "scale" : "2x",
-      "size" : "86x86",
-      "subtype" : "38mm"
-    },
-    {
-      "filename" : "196.png",
-      "idiom" : "watch",
-      "role" : "quickLook",
-      "scale" : "2x",
-      "size" : "98x98",
-      "subtype" : "42mm"
-    },
-    {
-      "filename" : "216.png",
-      "idiom" : "watch",
-      "role" : "quickLook",
-      "scale" : "2x",
-      "size" : "108x108",
-      "subtype" : "44mm"
-    },
-    {
-      "idiom" : "watch",
-      "role" : "quickLook",
-      "scale" : "2x",
-      "size" : "117x117",
-      "subtype" : "45mm"
-    },
-    {
-      "idiom" : "watch",
-      "role" : "quickLook",
-      "scale" : "2x",
-      "size" : "129x129",
-      "subtype" : "49mm"
-    },
-    {
-      "filename" : "1024.png",
-      "idiom" : "watch-marketing",
-      "scale" : "1x",
-      "size" : "1024x1024"
-    }
-  ],
-  "info" : {
-    "author" : "xcode",
-    "version" : 1
-  }
-}
diff --git a/ios/app/Images.xcassets/Contents.json b/ios/app/Images.xcassets/Contents.json
deleted file mode 100644
index 73c00596a..000000000
--- a/ios/app/Images.xcassets/Contents.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
-  "info" : {
-    "author" : "xcode",
-    "version" : 1
-  }
-}
diff --git a/ios/app/Images.xcassets/LaunchScreen.imageset/bsky-app-splash.png b/ios/app/Images.xcassets/LaunchScreen.imageset/bsky-app-splash.png
deleted file mode 100644
index 188625331..000000000
--- a/ios/app/Images.xcassets/LaunchScreen.imageset/bsky-app-splash.png
+++ /dev/null
Binary files differdiff --git a/ios/app/Images.xcassets/LaunchScreen.imageset/bsky-app-splash@2x.png b/ios/app/Images.xcassets/LaunchScreen.imageset/bsky-app-splash@2x.png
deleted file mode 100644
index 02e12c9ad..000000000
--- a/ios/app/Images.xcassets/LaunchScreen.imageset/bsky-app-splash@2x.png
+++ /dev/null
Binary files differdiff --git a/ios/app/Images.xcassets/LaunchScreen.imageset/bsky-app-splash@3x.png b/ios/app/Images.xcassets/LaunchScreen.imageset/bsky-app-splash@3x.png
deleted file mode 100644
index 12be9bc0c..000000000
--- a/ios/app/Images.xcassets/LaunchScreen.imageset/bsky-app-splash@3x.png
+++ /dev/null
Binary files differdiff --git a/ios/app/Info.plist b/ios/app/Info.plist
deleted file mode 100644
index 779b86ea6..000000000
--- a/ios/app/Info.plist
+++ /dev/null
@@ -1,86 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-	<key>BGTaskSchedulerPermittedIdentifiers</key>
-	<array>
-		<string>com.transistorsoft.fetch</string>
-	</array>
-	<key>CFBundleDevelopmentRegion</key>
-	<string>en</string>
-	<key>CFBundleDisplayName</key>
-	<string>Bluesky</string>
-	<key>CFBundleExecutable</key>
-	<string>$(EXECUTABLE_NAME)</string>
-	<key>CFBundleIdentifier</key>
-	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
-	<key>CFBundleInfoDictionaryVersion</key>
-	<string>6.0</string>
-	<key>CFBundleName</key>
-	<string>$(PRODUCT_NAME)</string>
-	<key>CFBundlePackageType</key>
-	<string>APPL</string>
-	<key>CFBundleShortVersionString</key>
-	<string>1.6</string>
-	<key>CFBundleSignature</key>
-	<string>????</string>
-	<key>CFBundleURLTypes</key>
-	<array>
-		<dict>
-			<key>CFBundleTypeRole</key>
-			<string>Editor</string>
-			<key>CFBundleURLName</key>
-			<string>xyz.blueskyweb.app</string>
-			<key>CFBundleURLSchemes</key>
-			<array>
-				<string>bskyapp</string>
-			</array>
-		</dict>
-	</array>
-	<key>CFBundleVersion</key>
-	<string>1</string>
-	<key>ITSAppUsesNonExemptEncryption</key>
-	<false/>
-	<key>LSRequiresIPhoneOS</key>
-	<true/>
-	<key>NSAppTransportSecurity</key>
-	<dict>
-		<key>NSExceptionDomains</key>
-		<dict>
-			<key>localhost</key>
-			<dict>
-				<key>NSExceptionAllowsInsecureHTTPLoads</key>
-				<true/>
-			</dict>
-		</dict>
-	</dict>
-	<key>NSCameraUsageDescription</key>
-	<string>Used to take pictures and videos when composing posts, choosing avatars, and so on.</string>
-	<key>NSLocationWhenInUseUsageDescription</key>
-	<string></string>
-	<key>NSPhotoLibraryUsageDescription</key>
-	<string>Used to upload pictures and videos when composing posts, choosing avatars, and so on.</string>
-	<key>UIBackgroundModes</key>
-	<array>
-		<string>fetch</string>
-	</array>
-	<key>UILaunchStoryboardName</key>
-	<string>LaunchScreen</string>
-	<key>UIRequiredDeviceCapabilities</key>
-	<array>
-		<string>armv7</string>
-	</array>
-	<key>UISupportedInterfaceOrientations</key>
-	<array>
-		<string>UIInterfaceOrientationPortrait</string>
-	</array>
-	<key>UISupportedInterfaceOrientations~ipad</key>
-	<array>
-		<string>UIInterfaceOrientationLandscapeLeft</string>
-		<string>UIInterfaceOrientationLandscapeRight</string>
-		<string>UIInterfaceOrientationPortrait</string>
-	</array>
-	<key>UIViewControllerBasedStatusBarAppearance</key>
-	<false/>
-</dict>
-</plist>
diff --git a/ios/bluesky.xcodeproj/project.pbxproj b/ios/bluesky.xcodeproj/project.pbxproj
new file mode 100644
index 000000000..17aa51a7e
--- /dev/null
+++ b/ios/bluesky.xcodeproj/project.pbxproj
@@ -0,0 +1,521 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 46;
+	objects = {
+
+/* Begin PBXBuildFile section */
+		13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; };
+		13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
+		13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
+		3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */; };
+		96905EF65AED1B983A6B3ABC /* libPods-bluesky.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 58EEBF8E8E6FB1BC6CAF49B5 /* libPods-bluesky.a */; };
+		B18059E884C0ABDD17F3DC3D /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */; };
+		B6BE37E812824A869C1E16F6 /* noop-file.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FECE0FEBF1E4D88B437323C /* noop-file.swift */; };
+		BB2F792D24A3F905000567C9 /* Expo.plist in Resources */ = {isa = PBXBuildFile; fileRef = BB2F792C24A3F905000567C9 /* Expo.plist */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+		008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.jsbundle; sourceTree = "<group>"; };
+		13B07F961A680F5B00A75B9A /* bluesky.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = bluesky.app; sourceTree = BUILT_PRODUCTS_DIR; };
+		13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = bluesky/AppDelegate.h; sourceTree = "<group>"; };
+		13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = bluesky/AppDelegate.mm; sourceTree = "<group>"; };
+		13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = bluesky/Images.xcassets; sourceTree = "<group>"; };
+		13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = bluesky/Info.plist; sourceTree = "<group>"; };
+		13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = bluesky/main.m; sourceTree = "<group>"; };
+		4FECE0FEBF1E4D88B437323C /* noop-file.swift */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.swift; name = "noop-file.swift"; path = "bluesky/noop-file.swift"; sourceTree = "<group>"; };
+		58EEBF8E8E6FB1BC6CAF49B5 /* libPods-bluesky.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-bluesky.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+		6C2E3173556A471DD304B334 /* Pods-bluesky.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-bluesky.debug.xcconfig"; path = "Target Support Files/Pods-bluesky/Pods-bluesky.debug.xcconfig"; sourceTree = "<group>"; };
+		7A4D352CD337FB3A3BF06240 /* Pods-bluesky.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-bluesky.release.xcconfig"; path = "Target Support Files/Pods-bluesky/Pods-bluesky.release.xcconfig"; sourceTree = "<group>"; };
+		AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = bluesky/SplashScreen.storyboard; sourceTree = "<group>"; };
+		BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = "<group>"; };
+		ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
+		FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-bluesky/ExpoModulesProvider.swift"; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		13B07F8C1A680F5B00A75B9A /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				96905EF65AED1B983A6B3ABC /* libPods-bluesky.a in Frameworks */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		13B07FAE1A68108700A75B9A /* bluesky */ = {
+			isa = PBXGroup;
+			children = (
+				BB2F792B24A3F905000567C9 /* Supporting */,
+				008F07F21AC5B25A0029DE68 /* main.jsbundle */,
+				13B07FAF1A68108700A75B9A /* AppDelegate.h */,
+				13B07FB01A68108700A75B9A /* AppDelegate.mm */,
+				13B07FB51A68108700A75B9A /* Images.xcassets */,
+				13B07FB61A68108700A75B9A /* Info.plist */,
+				13B07FB71A68108700A75B9A /* main.m */,
+				AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */,
+				4FECE0FEBF1E4D88B437323C /* noop-file.swift */,
+			);
+			name = bluesky;
+			sourceTree = "<group>";
+		};
+		2D16E6871FA4F8E400B85C8A /* Frameworks */ = {
+			isa = PBXGroup;
+			children = (
+				ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
+				58EEBF8E8E6FB1BC6CAF49B5 /* libPods-bluesky.a */,
+			);
+			name = Frameworks;
+			sourceTree = "<group>";
+		};
+		832341AE1AAA6A7D00B99B32 /* Libraries */ = {
+			isa = PBXGroup;
+			children = (
+			);
+			name = Libraries;
+			sourceTree = "<group>";
+		};
+		83CBB9F61A601CBA00E9B192 = {
+			isa = PBXGroup;
+			children = (
+				13B07FAE1A68108700A75B9A /* bluesky */,
+				832341AE1AAA6A7D00B99B32 /* Libraries */,
+				83CBBA001A601CBA00E9B192 /* Products */,
+				2D16E6871FA4F8E400B85C8A /* Frameworks */,
+				D65327D7A22EEC0BE12398D9 /* Pods */,
+				D7E4C46ADA2E9064B798F356 /* ExpoModulesProviders */,
+			);
+			indentWidth = 2;
+			sourceTree = "<group>";
+			tabWidth = 2;
+			usesTabs = 0;
+		};
+		83CBBA001A601CBA00E9B192 /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				13B07F961A680F5B00A75B9A /* bluesky.app */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		92DBD88DE9BF7D494EA9DA96 /* bluesky */ = {
+			isa = PBXGroup;
+			children = (
+				FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */,
+			);
+			name = bluesky;
+			sourceTree = "<group>";
+		};
+		BB2F792B24A3F905000567C9 /* Supporting */ = {
+			isa = PBXGroup;
+			children = (
+				BB2F792C24A3F905000567C9 /* Expo.plist */,
+			);
+			name = Supporting;
+			path = bluesky/Supporting;
+			sourceTree = "<group>";
+		};
+		D65327D7A22EEC0BE12398D9 /* Pods */ = {
+			isa = PBXGroup;
+			children = (
+				6C2E3173556A471DD304B334 /* Pods-bluesky.debug.xcconfig */,
+				7A4D352CD337FB3A3BF06240 /* Pods-bluesky.release.xcconfig */,
+			);
+			path = Pods;
+			sourceTree = "<group>";
+		};
+		D7E4C46ADA2E9064B798F356 /* ExpoModulesProviders */ = {
+			isa = PBXGroup;
+			children = (
+				92DBD88DE9BF7D494EA9DA96 /* bluesky */,
+			);
+			name = ExpoModulesProviders;
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+		13B07F861A680F5B00A75B9A /* bluesky */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "bluesky" */;
+			buildPhases = (
+				08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */,
+				FD10A7F022414F080027D42C /* Start Packager */,
+				13B07F871A680F5B00A75B9A /* Sources */,
+				13B07F8C1A680F5B00A75B9A /* Frameworks */,
+				13B07F8E1A680F5B00A75B9A /* Resources */,
+				00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
+				800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */,
+				0FD458797140490C54BA8FEC /* [CP] Embed Pods Frameworks */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = bluesky;
+			productName = bluesky;
+			productReference = 13B07F961A680F5B00A75B9A /* bluesky.app */;
+			productType = "com.apple.product-type.application";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		83CBB9F71A601CBA00E9B192 /* Project object */ = {
+			isa = PBXProject;
+			attributes = {
+				LastUpgradeCheck = 1130;
+				TargetAttributes = {
+					13B07F861A680F5B00A75B9A = {
+						DevelopmentTeam = B3LX46C5HS;
+						LastSwiftMigration = 1250;
+					};
+				};
+			};
+			buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "bluesky" */;
+			compatibilityVersion = "Xcode 3.2";
+			developmentRegion = en;
+			hasScannedForEncodings = 0;
+			knownRegions = (
+				en,
+				Base,
+			);
+			mainGroup = 83CBB9F61A601CBA00E9B192;
+			productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
+			projectDirPath = "";
+			projectRoot = "";
+			targets = (
+				13B07F861A680F5B00A75B9A /* bluesky */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+		13B07F8E1A680F5B00A75B9A /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				BB2F792D24A3F905000567C9 /* Expo.plist in Resources */,
+				13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
+				3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+		00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputPaths = (
+			);
+			name = "Bundle React Native code and images";
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "if [[ -f \"$PODS_ROOT/../.xcode.env\" ]]; then\n  source \"$PODS_ROOT/../.xcode.env\"\nfi\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n  source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n# The project root by default is one level up from the ios directory\nexport PROJECT_ROOT=\"$PROJECT_DIR\"/..\n\nif [[ \"$CONFIGURATION\" = *Debug* ]]; then\n  export SKIP_BUNDLING=1\nfi\nif [[ -z \"$ENTRY_FILE\" ]]; then\n  # Set the entry JS file using the bundler's entry resolution.\n  export ENTRY_FILE=\"$(\"$NODE_BINARY\" -e \"require('expo/scripts/resolveAppEntry')\" $PROJECT_ROOT ios relative | tail -n 1)\"\nfi\n\n`\"$NODE_BINARY\" --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\"`\n\n";
+		};
+		08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputFileListPaths = (
+			);
+			inputPaths = (
+				"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+				"${PODS_ROOT}/Manifest.lock",
+			);
+			name = "[CP] Check Pods Manifest.lock";
+			outputFileListPaths = (
+			);
+			outputPaths = (
+				"$(DERIVED_FILE_DIR)/Pods-bluesky-checkManifestLockResult.txt",
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+			showEnvVarsInLog = 0;
+		};
+		0FD458797140490C54BA8FEC /* [CP] Embed Pods Frameworks */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputPaths = (
+				"${PODS_ROOT}/Target Support Files/Pods-bluesky/Pods-bluesky-frameworks.sh",
+				"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes",
+			);
+			name = "[CP] Embed Pods Frameworks";
+			outputPaths = (
+				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-bluesky/Pods-bluesky-frameworks.sh\"\n";
+			showEnvVarsInLog = 0;
+		};
+		800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputPaths = (
+				"${PODS_ROOT}/Target Support Files/Pods-bluesky/Pods-bluesky-resources.sh",
+				"${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle",
+				"${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle",
+				"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle",
+				"${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle",
+				"${PODS_CONFIGURATION_BUILD_DIR}/expo-dev-launcher/EXDevLauncher.bundle",
+				"${PODS_CONFIGURATION_BUILD_DIR}/expo-dev-menu/EXDevMenu.bundle",
+			);
+			name = "[CP] Copy Pods Resources";
+			outputPaths = (
+				"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle",
+				"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle",
+				"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle",
+				"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle",
+				"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXDevLauncher.bundle",
+				"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXDevMenu.bundle",
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-bluesky/Pods-bluesky-resources.sh\"\n";
+			showEnvVarsInLog = 0;
+		};
+		FD10A7F022414F080027D42C /* Start Packager */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputFileListPaths = (
+			);
+			inputPaths = (
+			);
+			name = "Start Packager";
+			outputFileListPaths = (
+			);
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "if [[ -f \"$PODS_ROOT/../.xcode.env\" ]]; then\n  source \"$PODS_ROOT/../.xcode.env\"\nfi\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n  source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\nexport RCT_METRO_PORT=\"${RCT_METRO_PORT:=8081}\"\necho \"export RCT_METRO_PORT=${RCT_METRO_PORT}\" > `$NODE_BINARY --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/.packager.env'\"`\nif [ -z \"${RCT_NO_LAUNCH_PACKAGER+xxx}\" ] ; then\n  if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then\n    if ! curl -s \"http://localhost:${RCT_METRO_PORT}/status\" | grep -q \"packager-status:running\" ; then\n      echo \"Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly\"\n      exit 2\n    fi\n  else\n    open `$NODE_BINARY --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/launchPackager.command'\"` || echo \"Can't start packager automatically\"\n  fi\nfi\n";
+			showEnvVarsInLog = 0;
+		};
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+		13B07F871A680F5B00A75B9A /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */,
+				13B07FC11A68108700A75B9A /* main.m in Sources */,
+				B18059E884C0ABDD17F3DC3D /* ExpoModulesProvider.swift in Sources */,
+				B6BE37E812824A869C1E16F6 /* noop-file.swift in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+		13B07F941A680F5B00A75B9A /* Debug */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 6C2E3173556A471DD304B334 /* Pods-bluesky.debug.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CLANG_ENABLE_MODULES = YES;
+				CODE_SIGN_ENTITLEMENTS = bluesky/bluesky.entitlements;
+				CURRENT_PROJECT_VERSION = 1;
+				DEVELOPMENT_TEAM = B3LX46C5HS;
+				ENABLE_BITCODE = NO;
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"$(inherited)",
+					"FB_SONARKIT_ENABLED=1",
+				);
+				INFOPLIST_FILE = bluesky/Info.plist;
+				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+				MARKETING_VERSION = 1.0;
+				OTHER_LDFLAGS = (
+					"$(inherited)",
+					"-ObjC",
+					"-lc++",
+				);
+				OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
+				PRODUCT_BUNDLE_IDENTIFIER = xyz.blueskyweb.app;
+				PRODUCT_NAME = bluesky;
+				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+				SWIFT_VERSION = 5.0;
+				TARGETED_DEVICE_FAMILY = "1,2";
+				VERSIONING_SYSTEM = "apple-generic";
+			};
+			name = Debug;
+		};
+		13B07F951A680F5B00A75B9A /* Release */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 7A4D352CD337FB3A3BF06240 /* Pods-bluesky.release.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CLANG_ENABLE_MODULES = YES;
+				CODE_SIGN_ENTITLEMENTS = bluesky/bluesky.entitlements;
+				CURRENT_PROJECT_VERSION = 1;
+				DEVELOPMENT_TEAM = B3LX46C5HS;
+				INFOPLIST_FILE = bluesky/Info.plist;
+				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+				MARKETING_VERSION = 1.0;
+				OTHER_LDFLAGS = (
+					"$(inherited)",
+					"-ObjC",
+					"-lc++",
+				);
+				OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
+				PRODUCT_BUNDLE_IDENTIFIER = xyz.blueskyweb.app;
+				PRODUCT_NAME = bluesky;
+				SWIFT_VERSION = 5.0;
+				TARGETED_DEVICE_FAMILY = "1,2";
+				VERSIONING_SYSTEM = "apple-generic";
+			};
+			name = Release;
+		};
+		83CBBA201A601CBA00E9B192 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
+				CLANG_CXX_LANGUAGE_STANDARD = "c++17";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+				COPY_PHASE_STRIP = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				ENABLE_TESTABILITY = YES;
+				"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_DYNAMIC_NO_PIC = NO;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"DEBUG=1",
+					"$(inherited)",
+				);
+				GCC_SYMBOLS_PRIVATE_EXTERN = NO;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
+				LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)";
+				LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\"";
+				MTL_ENABLE_DEBUG_INFO = YES;
+				ONLY_ACTIVE_ARCH = YES;
+				REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
+				SDKROOT = iphoneos;
+			};
+			name = Debug;
+		};
+		83CBBA211A601CBA00E9B192 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
+				CLANG_CXX_LANGUAGE_STANDARD = "c++17";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+				COPY_PHASE_STRIP = YES;
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
+				LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)";
+				LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\"";
+				MTL_ENABLE_DEBUG_INFO = NO;
+				REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
+				SDKROOT = iphoneos;
+				VALIDATE_PRODUCT = YES;
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "bluesky" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				13B07F941A680F5B00A75B9A /* Debug */,
+				13B07F951A680F5B00A75B9A /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "bluesky" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				83CBBA201A601CBA00E9B192 /* Debug */,
+				83CBBA211A601CBA00E9B192 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+/* End XCConfigurationList section */
+	};
+	rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */;
+}
diff --git a/ios/bluesky.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/bluesky.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 000000000..919434a62
--- /dev/null
+++ b/ios/bluesky.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "self:">
+   </FileRef>
+</Workspace>
diff --git a/ios/app.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/bluesky.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
index 18d981003..18d981003 100644
--- a/ios/app.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
+++ b/ios/bluesky.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
diff --git a/ios/app.xcodeproj/xcshareddata/xcschemes/app.xcscheme b/ios/bluesky.xcodeproj/xcshareddata/xcschemes/bluesky.xcscheme
index 326123a32..802b7e18c 100644
--- a/ios/app.xcodeproj/xcshareddata/xcschemes/app.xcscheme
+++ b/ios/bluesky.xcodeproj/xcshareddata/xcschemes/bluesky.xcscheme
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "1340"
+   LastUpgradeVersion = "1130"
    version = "1.3">
    <BuildAction
       parallelizeBuildables = "YES"
@@ -15,9 +15,9 @@
             <BuildableReference
                BuildableIdentifier = "primary"
                BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
-               BuildableName = "app.app"
-               BlueprintName = "app"
-               ReferencedContainer = "container:app.xcodeproj">
+               BuildableName = "bluesky.app"
+               BlueprintName = "bluesky"
+               ReferencedContainer = "container:bluesky.xcodeproj">
             </BuildableReference>
          </BuildActionEntry>
       </BuildActionEntries>
@@ -33,9 +33,9 @@
             <BuildableReference
                BuildableIdentifier = "primary"
                BlueprintIdentifier = "00E356ED1AD99517003FC87E"
-               BuildableName = "appTests.xctest"
-               BlueprintName = "appTests"
-               ReferencedContainer = "container:app.xcodeproj">
+               BuildableName = "blueskyTests.xctest"
+               BlueprintName = "blueskyTests"
+               ReferencedContainer = "container:bluesky.xcodeproj">
             </BuildableReference>
          </TestableReference>
       </Testables>
@@ -55,9 +55,9 @@
          <BuildableReference
             BuildableIdentifier = "primary"
             BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
-            BuildableName = "app.app"
-            BlueprintName = "app"
-            ReferencedContainer = "container:app.xcodeproj">
+            BuildableName = "bluesky.app"
+            BlueprintName = "bluesky"
+            ReferencedContainer = "container:bluesky.xcodeproj">
          </BuildableReference>
       </BuildableProductRunnable>
    </LaunchAction>
@@ -72,9 +72,9 @@
          <BuildableReference
             BuildableIdentifier = "primary"
             BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
-            BuildableName = "app.app"
-            BlueprintName = "app"
-            ReferencedContainer = "container:app.xcodeproj">
+            BuildableName = "bluesky.app"
+            BlueprintName = "bluesky"
+            ReferencedContainer = "container:bluesky.xcodeproj">
          </BuildableReference>
       </BuildableProductRunnable>
    </ProfileAction>
diff --git a/ios/app.xcworkspace/contents.xcworkspacedata b/ios/bluesky.xcworkspace/contents.xcworkspacedata
index b83e63c38..402cef170 100644
--- a/ios/app.xcworkspace/contents.xcworkspacedata
+++ b/ios/bluesky.xcworkspace/contents.xcworkspacedata
@@ -2,7 +2,7 @@
 <Workspace
    version = "1.0">
    <FileRef
-      location = "group:app.xcodeproj">
+      location = "group:bluesky.xcodeproj">
    </FileRef>
    <FileRef
       location = "group:Pods/Pods.xcodeproj">
diff --git a/ios/app/app.entitlements b/ios/bluesky.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
index 05af7cbda..18d981003 100644
--- a/ios/app/app.entitlements
+++ b/ios/bluesky.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -2,9 +2,7 @@
 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <plist version="1.0">
 <dict>
-	<key>com.apple.developer.associated-domains</key>
-	<array>
-		<string>applinks:bsky.app</string>
-	</array>
+	<key>IDEDidComputeMac32BitWarning</key>
+	<true/>
 </dict>
 </plist>
diff --git a/ios/bluesky/AppDelegate.h b/ios/bluesky/AppDelegate.h
new file mode 100644
index 000000000..eaba2fa40
--- /dev/null
+++ b/ios/bluesky/AppDelegate.h
@@ -0,0 +1,7 @@
+#import <RCTAppDelegate.h>
+#import <UIKit/UIKit.h>
+#import <Expo/Expo.h>
+
+@interface AppDelegate : EXAppDelegateWrapper
+
+@end
\ No newline at end of file
diff --git a/ios/bluesky/AppDelegate.mm b/ios/bluesky/AppDelegate.mm
new file mode 100644
index 000000000..a358131c0
--- /dev/null
+++ b/ios/bluesky/AppDelegate.mm
@@ -0,0 +1,72 @@
+#import "AppDelegate.h"
+
+#import <React/RCTBundleURLProvider.h>
+#import <React/RCTLinkingManager.h>
+
+#import <TSBackgroundFetch/TSBackgroundFetch.h>
+
+@implementation AppDelegate
+
+- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
+{
+  self.moduleName = @"main";
+
+  // You can add your custom initial props in the dictionary below.
+  // They will be passed down to the ViewController used by React Native.
+  self.initialProps = @{};
+  
+  // Register BackgroundFetch
+  [[TSBackgroundFetch sharedInstance] didFinishLaunching];
+
+  return [super application:application didFinishLaunchingWithOptions:launchOptions];
+}
+
+- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
+{
+#if DEBUG
+  return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
+#else
+  return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
+#endif
+}
+
+/// This method controls whether the `concurrentRoot`feature of React18 is turned on or off.
+///
+/// @see: https://reactjs.org/blog/2022/03/29/react-v18.html
+/// @note: This requires to be rendering on Fabric (i.e. on the New Architecture).
+/// @return: `true` if the `concurrentRoot` feature is enabled. Otherwise, it returns `false`.
+- (BOOL)concurrentRootEnabled
+{
+  return true;
+}
+
+// Linking API
+- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
+  return [super application:application openURL:url options:options] || [RCTLinkingManager application:application openURL:url options:options];
+}
+
+// Universal Links
+- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {
+  BOOL result = [RCTLinkingManager application:application continueUserActivity:userActivity restorationHandler:restorationHandler];
+  return [super application:application continueUserActivity:userActivity restorationHandler:restorationHandler] || result;
+}
+
+// Explicitly define remote notification delegates to ensure compatibility with some third-party libraries
+- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
+{
+  return [super application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
+}
+
+// Explicitly define remote notification delegates to ensure compatibility with some third-party libraries
+- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
+{
+  return [super application:application didFailToRegisterForRemoteNotificationsWithError:error];
+}
+
+// Explicitly define remote notification delegates to ensure compatibility with some third-party libraries
+- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
+{
+  return [super application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];
+}
+
+@end
\ No newline at end of file
diff --git a/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-20x20@1x.png b/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-20x20@1x.png
new file mode 100644
index 000000000..fea9a3533
--- /dev/null
+++ b/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-20x20@1x.png
Binary files differdiff --git a/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-20x20@2x.png b/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-20x20@2x.png
new file mode 100644
index 000000000..299b8c100
--- /dev/null
+++ b/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-20x20@2x.png
Binary files differdiff --git a/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-20x20@3x.png b/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-20x20@3x.png
new file mode 100644
index 000000000..30fe7e1f0
--- /dev/null
+++ b/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-20x20@3x.png
Binary files differdiff --git a/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-29x29@1x.png b/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-29x29@1x.png
new file mode 100644
index 000000000..ba88e1111
--- /dev/null
+++ b/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-29x29@1x.png
Binary files differdiff --git a/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-29x29@2x.png b/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-29x29@2x.png
new file mode 100644
index 000000000..093b2f931
--- /dev/null
+++ b/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-29x29@2x.png
Binary files differdiff --git a/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-29x29@3x.png b/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-29x29@3x.png
new file mode 100644
index 000000000..f391f3662
--- /dev/null
+++ b/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-29x29@3x.png
Binary files differdiff --git a/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-40x40@1x.png b/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-40x40@1x.png
new file mode 100644
index 000000000..299b8c100
--- /dev/null
+++ b/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-40x40@1x.png
Binary files differdiff --git a/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-40x40@2x.png b/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-40x40@2x.png
new file mode 100644
index 000000000..140a420ab
--- /dev/null
+++ b/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-40x40@2x.png
Binary files differdiff --git a/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-40x40@3x.png b/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-40x40@3x.png
new file mode 100644
index 000000000..f8e459304
--- /dev/null
+++ b/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-40x40@3x.png
Binary files differdiff --git a/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-60x60@2x.png b/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-60x60@2x.png
new file mode 100644
index 000000000..f8e459304
--- /dev/null
+++ b/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-60x60@2x.png
Binary files differdiff --git a/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-60x60@3x.png b/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-60x60@3x.png
new file mode 100644
index 000000000..0a2ce9f15
--- /dev/null
+++ b/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-60x60@3x.png
Binary files differdiff --git a/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-76x76@1x.png b/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-76x76@1x.png
new file mode 100644
index 000000000..cb5bd1988
--- /dev/null
+++ b/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-76x76@1x.png
Binary files differdiff --git a/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-76x76@2x.png b/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-76x76@2x.png
new file mode 100644
index 000000000..a416fe21a
--- /dev/null
+++ b/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-76x76@2x.png
Binary files differdiff --git a/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-83.5x83.5@2x.png b/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-83.5x83.5@2x.png
new file mode 100644
index 000000000..4263af789
--- /dev/null
+++ b/ios/bluesky/Images.xcassets/AppIcon.appiconset/App-Icon-83.5x83.5@2x.png
Binary files differdiff --git a/ios/bluesky/Images.xcassets/AppIcon.appiconset/Contents.json b/ios/bluesky/Images.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 000000000..f920cb0ec
--- /dev/null
+++ b/ios/bluesky/Images.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,122 @@
+{
+  "images": [
+    {
+      "idiom": "iphone",
+      "size": "20x20",
+      "scale": "2x",
+      "filename": "App-Icon-20x20@2x.png"
+    },
+    {
+      "idiom": "iphone",
+      "size": "20x20",
+      "scale": "3x",
+      "filename": "App-Icon-20x20@3x.png"
+    },
+    {
+      "idiom": "iphone",
+      "size": "29x29",
+      "scale": "1x",
+      "filename": "App-Icon-29x29@1x.png"
+    },
+    {
+      "idiom": "iphone",
+      "size": "29x29",
+      "scale": "2x",
+      "filename": "App-Icon-29x29@2x.png"
+    },
+    {
+      "idiom": "iphone",
+      "size": "29x29",
+      "scale": "3x",
+      "filename": "App-Icon-29x29@3x.png"
+    },
+    {
+      "idiom": "iphone",
+      "size": "40x40",
+      "scale": "2x",
+      "filename": "App-Icon-40x40@2x.png"
+    },
+    {
+      "idiom": "iphone",
+      "size": "40x40",
+      "scale": "3x",
+      "filename": "App-Icon-40x40@3x.png"
+    },
+    {
+      "idiom": "iphone",
+      "size": "60x60",
+      "scale": "2x",
+      "filename": "App-Icon-60x60@2x.png"
+    },
+    {
+      "idiom": "iphone",
+      "size": "60x60",
+      "scale": "3x",
+      "filename": "App-Icon-60x60@3x.png"
+    },
+    {
+      "idiom": "ipad",
+      "size": "20x20",
+      "scale": "1x",
+      "filename": "App-Icon-20x20@1x.png"
+    },
+    {
+      "idiom": "ipad",
+      "size": "20x20",
+      "scale": "2x",
+      "filename": "App-Icon-20x20@2x.png"
+    },
+    {
+      "idiom": "ipad",
+      "size": "29x29",
+      "scale": "1x",
+      "filename": "App-Icon-29x29@1x.png"
+    },
+    {
+      "idiom": "ipad",
+      "size": "29x29",
+      "scale": "2x",
+      "filename": "App-Icon-29x29@2x.png"
+    },
+    {
+      "idiom": "ipad",
+      "size": "40x40",
+      "scale": "1x",
+      "filename": "App-Icon-40x40@1x.png"
+    },
+    {
+      "idiom": "ipad",
+      "size": "40x40",
+      "scale": "2x",
+      "filename": "App-Icon-40x40@2x.png"
+    },
+    {
+      "idiom": "ipad",
+      "size": "76x76",
+      "scale": "1x",
+      "filename": "App-Icon-76x76@1x.png"
+    },
+    {
+      "idiom": "ipad",
+      "size": "76x76",
+      "scale": "2x",
+      "filename": "App-Icon-76x76@2x.png"
+    },
+    {
+      "idiom": "ipad",
+      "size": "83.5x83.5",
+      "scale": "2x",
+      "filename": "App-Icon-83.5x83.5@2x.png"
+    },
+    {
+      "idiom": "ios-marketing",
+      "size": "1024x1024",
+      "scale": "1x",
+      "filename": "ItunesArtwork@2x.png"
+    }
+  ],
+  "info": {
+    "version": 1,
+    "author": "expo"
+  }
+}
\ No newline at end of file
diff --git a/ios/bluesky/Images.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png b/ios/bluesky/Images.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png
new file mode 100644
index 000000000..35e5b79b2
--- /dev/null
+++ b/ios/bluesky/Images.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png
Binary files differdiff --git a/ios/bluesky/Images.xcassets/Contents.json b/ios/bluesky/Images.xcassets/Contents.json
new file mode 100644
index 000000000..ed285c2e5
--- /dev/null
+++ b/ios/bluesky/Images.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+  "info" : {
+    "version" : 1,
+    "author" : "expo"
+  }
+}
diff --git a/ios/bluesky/Images.xcassets/SplashScreen.imageset/Contents.json b/ios/bluesky/Images.xcassets/SplashScreen.imageset/Contents.json
new file mode 100644
index 000000000..3cf848977
--- /dev/null
+++ b/ios/bluesky/Images.xcassets/SplashScreen.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+  "images": [
+    {
+      "idiom": "universal",
+      "filename": "image.png",
+      "scale": "1x"
+    },
+    {
+      "idiom": "universal",
+      "scale": "2x"
+    },
+    {
+      "idiom": "universal",
+      "scale": "3x"
+    }
+  ],
+  "info": {
+    "version": 1,
+    "author": "expo"
+  }
+}
\ No newline at end of file
diff --git a/ios/bluesky/Images.xcassets/SplashScreen.imageset/image.png b/ios/bluesky/Images.xcassets/SplashScreen.imageset/image.png
new file mode 100644
index 000000000..e0e67b90a
--- /dev/null
+++ b/ios/bluesky/Images.xcassets/SplashScreen.imageset/image.png
Binary files differdiff --git a/ios/app/Images.xcassets/LaunchScreen.imageset/Contents.json b/ios/bluesky/Images.xcassets/SplashScreenBackground.imageset/Contents.json
index 6ed6bc6f1..ded1a4df0 100644
--- a/ios/app/Images.xcassets/LaunchScreen.imageset/Contents.json
+++ b/ios/bluesky/Images.xcassets/SplashScreenBackground.imageset/Contents.json
@@ -1,17 +1,17 @@
 {
   "images" : [
     {
-      "filename" : "bsky-app-splash.png",
+      "filename" : "icon 2.png",
       "idiom" : "universal",
       "scale" : "1x"
     },
     {
-      "filename" : "bsky-app-splash@2x.png",
+      "filename" : "icon.png",
       "idiom" : "universal",
       "scale" : "2x"
     },
     {
-      "filename" : "bsky-app-splash@3x.png",
+      "filename" : "icon 1.png",
       "idiom" : "universal",
       "scale" : "3x"
     }
diff --git a/ios/bluesky/Images.xcassets/SplashScreenBackground.imageset/icon 1.png b/ios/bluesky/Images.xcassets/SplashScreenBackground.imageset/icon 1.png
new file mode 100644
index 000000000..1dda9a342
--- /dev/null
+++ b/ios/bluesky/Images.xcassets/SplashScreenBackground.imageset/icon 1.png
Binary files differdiff --git a/ios/bluesky/Images.xcassets/SplashScreenBackground.imageset/icon 2.png b/ios/bluesky/Images.xcassets/SplashScreenBackground.imageset/icon 2.png
new file mode 100644
index 000000000..1dda9a342
--- /dev/null
+++ b/ios/bluesky/Images.xcassets/SplashScreenBackground.imageset/icon 2.png
Binary files differdiff --git a/ios/bluesky/Images.xcassets/SplashScreenBackground.imageset/icon.png b/ios/bluesky/Images.xcassets/SplashScreenBackground.imageset/icon.png
new file mode 100644
index 000000000..1dda9a342
--- /dev/null
+++ b/ios/bluesky/Images.xcassets/SplashScreenBackground.imageset/icon.png
Binary files differdiff --git a/ios/bluesky/Info.plist b/ios/bluesky/Info.plist
new file mode 100644
index 000000000..eb3798dc3
--- /dev/null
+++ b/ios/bluesky/Info.plist
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+  <dict>
+    <key>BGTaskSchedulerPermittedIdentifiers</key>
+    <array>
+      <string>com.transistorsoft.fetch</string>
+    </array>
+    <key>CFBundleDevelopmentRegion</key>
+    <string>$(DEVELOPMENT_LANGUAGE)</string>
+    <key>CFBundleDisplayName</key>
+    <string>Bluesky</string>
+    <key>CFBundleExecutable</key>
+    <string>$(EXECUTABLE_NAME)</string>
+    <key>CFBundleIdentifier</key>
+    <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+    <key>CFBundleInfoDictionaryVersion</key>
+    <string>6.0</string>
+    <key>CFBundleName</key>
+    <string>$(PRODUCT_NAME)</string>
+    <key>CFBundlePackageType</key>
+    <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
+    <key>CFBundleShortVersionString</key>
+    <string>1.7</string>
+    <key>CFBundleSignature</key>
+    <string>????</string>
+    <key>CFBundleURLTypes</key>
+    <array>
+      <dict>
+        <key>CFBundleURLSchemes</key>
+        <array>
+          <string>xyz.blueskyweb.app</string>
+        </array>
+      </dict>
+      <dict>
+        <key>CFBundleURLSchemes</key>
+        <array>
+          <string>exp+bluesky</string>
+        </array>
+      </dict>
+    </array>
+    <key>CFBundleVersion</key>
+    <string>1</string>
+    <key>ITSAppUsesNonExemptEncryption</key>
+    <false/>
+    <key>LSRequiresIPhoneOS</key>
+    <true/>
+    <key>NSAppTransportSecurity</key>
+    <dict>
+      <key>NSAllowsArbitraryLoads</key>
+      <true/>
+      <key>NSExceptionDomains</key>
+      <dict>
+        <key>localhost</key>
+        <dict>
+          <key>NSExceptionAllowsInsecureHTTPLoads</key>
+          <true/>
+        </dict>
+      </dict>
+    </dict>
+    <key>NSCameraUsageDescription</key>
+    <string>Used to take pictures and videos when composing posts, choosing avatars, and so on.</string>
+    <key>NSMicrophoneUsageDescription</key>
+    <string>Used to take videos when composing posts.</string>
+    <key>NSLocationWhenInUseUsageDescription</key>
+    <string></string>
+    <key>NSPhotoLibraryUsageDescription</key>
+    <string>Used to upload pictures and videos when composing posts, choosing avatars, and so on.</string>
+    <key>UIBackgroundModes</key>
+    <array>
+      <string>fetch</string>
+    </array>
+    <key>UILaunchStoryboardName</key>
+    <string>SplashScreen</string>
+    <key>UIRequiredDeviceCapabilities</key>
+    <array>
+      <string>armv7</string>
+    </array>
+    <key>UIRequiresFullScreen</key>
+    <true/>
+    <key>UIStatusBarStyle</key>
+    <string>UIStatusBarStyleDefault</string>
+    <key>UISupportedInterfaceOrientations</key>
+    <array>
+      <string>UIInterfaceOrientationPortrait</string>
+    </array>
+    <key>UISupportedInterfaceOrientations~ipad</key>
+    <array>
+      <string>UIInterfaceOrientationLandscapeLeft</string>
+      <string>UIInterfaceOrientationLandscapeRight</string>
+      <string>UIInterfaceOrientationPortrait</string>
+    </array>
+    <key>UIUserInterfaceStyle</key>
+    <string>Light</string>
+    <key>UIViewControllerBasedStatusBarAppearance</key>
+    <false/>
+    <key>ITSAppUsesNonExemptEncryption</key>
+    <false/>
+  </dict>
+</plist>
\ No newline at end of file
diff --git a/ios/bluesky/SplashScreen.storyboard b/ios/bluesky/SplashScreen.storyboard
new file mode 100644
index 000000000..36ea46d75
--- /dev/null
+++ b/ios/bluesky/SplashScreen.storyboard
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="EXPO-VIEWCONTROLLER-1">
+    <device id="retina5_5" orientation="portrait" appearance="light"/>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
+        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <scenes>
+        <!--View Controller-->
+        <scene sceneID="EXPO-SCENE-1">
+            <objects>
+                <viewController storyboardIdentifier="SplashScreenViewController" id="EXPO-VIEWCONTROLLER-1" sceneMemberID="viewController">
+                    <view key="view" userInteractionEnabled="NO" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="EXPO-ContainerView" userLabel="ContainerView">
+                        <rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
+                        <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+                        <subviews>
+                            <imageView userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" insetsLayoutMarginsFromSafeArea="NO" image="SplashScreenBackground" translatesAutoresizingMaskIntoConstraints="NO" id="EXPO-SplashScreenBackground" userLabel="SplashScreenBackground">
+                                <rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
+                            </imageView>
+                            <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="SplashScreen" translatesAutoresizingMaskIntoConstraints="NO" id="EXPO-SplashScreen" userLabel="SplashScreen">
+                                <rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
+                            </imageView>
+                        </subviews>
+                        <viewLayoutGuide key="safeArea" id="Rmq-lb-GrQ"/>
+                        <constraints>
+                            <constraint firstItem="EXPO-SplashScreenBackground" firstAttribute="top" secondItem="EXPO-ContainerView" secondAttribute="top" id="1gX-mQ-vu6"/>
+                            <constraint firstItem="EXPO-SplashScreen" firstAttribute="top" secondItem="EXPO-ContainerView" secondAttribute="top" id="2VS-Uz-0LU"/>
+                            <constraint firstItem="EXPO-SplashScreen" firstAttribute="leading" secondItem="EXPO-ContainerView" secondAttribute="leading" id="61d16215e44b98e39d0a2c74fdbfaaa22601b12c"/>
+                            <constraint firstItem="EXPO-SplashScreenBackground" firstAttribute="leading" secondItem="EXPO-ContainerView" secondAttribute="leading" id="6tX-OG-Sck"/>
+                            <constraint firstItem="EXPO-SplashScreen" firstAttribute="top" secondItem="EXPO-ContainerView" secondAttribute="top" id="83fcb9b545b870ba44c24f0feeb116490c499c52"/>
+                            <constraint firstItem="EXPO-SplashScreenBackground" firstAttribute="trailing" secondItem="EXPO-ContainerView" secondAttribute="trailing" id="ABX-8g-7v4"/>
+                            <constraint firstItem="EXPO-SplashScreen" firstAttribute="trailing" secondItem="EXPO-ContainerView" secondAttribute="trailing" id="I6l-TP-6fn"/>
+                            <constraint firstItem="EXPO-SplashScreen" firstAttribute="leading" secondItem="EXPO-ContainerView" secondAttribute="leading" id="LhH-Ei-DKo"/>
+                            <constraint firstItem="EXPO-SplashScreen" firstAttribute="bottom" secondItem="EXPO-ContainerView" secondAttribute="bottom" id="d6a0be88096b36fb132659aa90203d39139deda9"/>
+                            <constraint firstItem="EXPO-SplashScreen" firstAttribute="trailing" secondItem="EXPO-ContainerView" secondAttribute="trailing" id="f934da460e9ab5acae3ad9987d5b676a108796c1"/>
+                            <constraint firstItem="EXPO-SplashScreenBackground" firstAttribute="bottom" secondItem="EXPO-ContainerView" secondAttribute="bottom" id="jkI-2V-eW5"/>
+                            <constraint firstItem="EXPO-SplashScreen" firstAttribute="bottom" secondItem="EXPO-ContainerView" secondAttribute="bottom" id="nbp-HC-eaG"/>
+                        </constraints>
+                    </view>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="EXPO-PLACEHOLDER-1" userLabel="First Responder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="140.625" y="129.4921875"/>
+        </scene>
+    </scenes>
+    <resources>
+        <image name="SplashScreen" width="600" height="900"/>
+        <image name="SplashScreenBackground" width="1" height="1"/>
+    </resources>
+</document>
diff --git a/ios/bluesky/Supporting/Expo.plist b/ios/bluesky/Supporting/Expo.plist
new file mode 100644
index 000000000..44b3d2428
--- /dev/null
+++ b/ios/bluesky/Supporting/Expo.plist
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+  <dict>
+    <key>EXUpdatesCheckOnLaunch</key>
+    <string>ALWAYS</string>
+    <key>EXUpdatesEnabled</key>
+    <true/>
+    <key>EXUpdatesLaunchWaitMs</key>
+    <integer>0</integer>
+    <key>EXUpdatesSDKVersion</key>
+    <string>48.0.0</string>
+    <key>EXUpdatesURL</key>
+    <string>https://exp.host/@arrygoo/bluesky</string>
+  </dict>
+</plist> 
\ No newline at end of file
diff --git a/ios/bluesky/bluesky.entitlements b/ios/bluesky/bluesky.entitlements
new file mode 100644
index 000000000..018a6e20c
--- /dev/null
+++ b/ios/bluesky/bluesky.entitlements
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+  <dict>
+    <key>aps-environment</key>
+    <string>development</string>
+  </dict>
+</plist>
\ No newline at end of file
diff --git a/ios/app/main.m b/ios/bluesky/main.m
index d645c7246..b1df44b95 100644
--- a/ios/app/main.m
+++ b/ios/bluesky/main.m
@@ -2,8 +2,7 @@
 
 #import "AppDelegate.h"
 
-int main(int argc, char *argv[])
-{
+int main(int argc, char * argv[]) {
   @autoreleasepool {
     return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
   }
diff --git a/ios/bluesky/noop-file.swift b/ios/bluesky/noop-file.swift
new file mode 100644
index 000000000..9835ba300
--- /dev/null
+++ b/ios/bluesky/noop-file.swift
@@ -0,0 +1,4 @@
+//
+// @generated
+// A blank Swift file must be created for native modules with Swift files to work correctly.
+//
\ No newline at end of file
diff --git a/jest/jestSetup.js b/jest/jestSetup.js
index e62a038a8..c3f82a872 100644
--- a/jest/jestSetup.js
+++ b/jest/jestSetup.js
@@ -64,6 +64,14 @@ jest.mock('@segment/analytics-react-native', () => ({
   }),
 }))
 
-jest.mock('react-native-permissions', () =>
-  require('react-native-permissions/mock'),
-)
+jest.mock('expo-camera', () => ({
+  Camera: {
+    useCameraPermissions: jest.fn(() => [true]),
+  },
+}))
+
+jest.mock('expo-media-library', () => ({
+  __esModule: true, // this property makes it work
+  default: jest.fn(),
+  usePermissions: jest.fn(() => [true]),
+}))
diff --git a/metro.config.js b/metro.config.js
index c81b3ca13..9e8e8745a 100644
--- a/metro.config.js
+++ b/metro.config.js
@@ -1,17 +1,3 @@
-/**
- * Metro configuration for React Native
- * https://github.com/facebook/react-native
- *
- * @format
- */
-
-module.exports = {
-  transformer: {
-    getTransformOptions: async () => ({
-      transform: {
-        experimentalImportSupport: false,
-        inlineRequires: true,
-      },
-    }),
-  },
-}
+// Learn more https://docs.expo.io/guides/customizing-metro
+const {getDefaultConfig} = require('expo/metro-config')
+module.exports = getDefaultConfig(__dirname)
diff --git a/package.json b/package.json
index 6f70f08b2..50a9e6129 100644
--- a/package.json
+++ b/package.json
@@ -1,12 +1,12 @@
 {
   "name": "bsky.app",
-  "version": "1.6.0",
+  "version": "1.7.0",
   "private": true,
   "scripts": {
-    "android": "react-native run-android",
-    "ios": "react-native run-ios --simulator=\"iPhone 14\"",
-    "web": "webpack-dev-server --config ./web/webpack.config.js -d inline-source-map --hot --color",
-    "start": "react-native start",
+    "android": "expo run:android",
+    "ios": "expo run:ios",
+    "web": "expo start --web",
+    "start": "expo start --dev-client",
     "clean-cache": "rm -rf node_modules/.cache/babel-loader/*",
     "test": "jest --forceExit --testTimeout=20000 --bail",
     "test-watch": "jest --watchAll",
@@ -20,6 +20,7 @@
     "@atproto/lexicon": "^0.0.4",
     "@atproto/xrpc": "^0.0.4",
     "@bam.tech/react-native-image-resizer": "^3.0.4",
+    "@expo/webpack-config": "^18.0.1",
     "@fortawesome/fontawesome-svg-core": "^6.1.1",
     "@fortawesome/free-regular-svg-icons": "^6.1.1",
     "@fortawesome/free-solid-svg-icons": "^6.1.1",
@@ -32,12 +33,33 @@
     "@react-native-camera-roll/camera-roll": "^5.2.2",
     "@react-native-clipboard/clipboard": "^1.10.0",
     "@react-native-community/blur": "^4.3.0",
+    "@react-navigation/bottom-tabs": "^6.5.7",
+    "@react-navigation/drawer": "^6.6.2",
+    "@react-navigation/native": "^6.1.6",
+    "@react-navigation/native-stack": "^6.9.12",
     "@segment/analytics-react-native": "^2.10.1",
     "@segment/sovran-react-native": "^0.4.5",
+    "@tiptap/core": "^2.0.0-beta.220",
+    "@tiptap/extension-document": "^2.0.0-beta.220",
+    "@tiptap/extension-link": "^2.0.0-beta.220",
+    "@tiptap/extension-mention": "^2.0.0-beta.220",
+    "@tiptap/extension-paragraph": "^2.0.0-beta.220",
+    "@tiptap/extension-placeholder": "^2.0.0-beta.220",
+    "@tiptap/extension-text": "^2.0.0-beta.220",
+    "@tiptap/pm": "^2.0.0-beta.220",
+    "@tiptap/react": "^2.0.0-beta.220",
+    "@tiptap/suggestion": "^2.0.0-beta.220",
     "@zxing/text-encoding": "^0.9.0",
     "await-lock": "^2.2.2",
     "base64-js": "^1.5.1",
     "email-validator": "^2.0.4",
+    "expo": "~48.0.0-beta.2",
+    "expo-camera": "~13.2.1",
+    "expo-dev-client": "~2.1.1",
+    "expo-image-picker": "~14.1.1",
+    "expo-media-library": "~15.2.1",
+    "expo-splash-screen": "~0.18.1",
+    "expo-status-bar": "~1.4.4",
     "he": "^1.2.0",
     "history": "^5.3.0",
     "js-sha256": "^0.9.0",
@@ -55,25 +77,24 @@
     "react-avatar-editor": "^13.0.0",
     "react-circular-progressbar": "^2.1.0",
     "react-dom": "^18.2.0",
-    "react-native": "0.71.1",
+    "react-native": "0.71.3",
     "react-native-appstate-hook": "^1.0.6",
     "react-native-background-fetch": "^4.1.8",
+    "react-native-drawer-layout": "^3.2.0",
     "react-native-fast-image": "^8.6.3",
     "react-native-fs": "^2.20.0",
-    "react-native-gesture-handler": "^2.5.0",
+    "react-native-gesture-handler": "~2.9.0",
     "react-native-haptic-feedback": "^1.14.0",
     "react-native-image-crop-picker": "^0.38.1",
     "react-native-inappbrowser-reborn": "^3.6.3",
     "react-native-linear-gradient": "^2.6.2",
-    "react-native-pager-view": "^6.0.2",
-    "react-native-permissions": "^3.6.1",
-    "react-native-progress": "^5.0.0",
-    "react-native-reanimated": "^2.9.1",
+    "react-native-progress": "bluesky-social/react-native-progress",
+    "react-native-reanimated": "~2.14.4",
     "react-native-root-siblings": "^4.1.1",
     "react-native-safe-area-context": "^4.4.1",
     "react-native-screens": "^3.13.1",
     "react-native-splash-screen": "^3.3.0",
-    "react-native-svg": "^12.4.0",
+    "react-native-svg": "^13.4.0",
     "react-native-tab-view": "^3.3.0",
     "react-native-url-polyfill": "^1.3.0",
     "react-native-uuid": "^2.0.1",
@@ -81,9 +102,10 @@
     "react-native-web": "^0.18.11",
     "react-native-web-linear-gradient": "^1.1.2",
     "react-native-web-webview": "^1.0.2",
-    "react-native-webview": "^11.26.1",
+    "react-native-webview": "11.26.0",
     "react-native-youtube-iframe": "^2.2.2",
     "rn-fetch-blob": "^0.12.0",
+    "tippy.js": "^6.3.7",
     "tlds": "^1.234.0",
     "zod": "^3.20.2"
   },
@@ -93,11 +115,11 @@
     "@babel/preset-env": "^7.20.0",
     "@babel/runtime": "^7.20.0",
     "@react-native-community/eslint-config": "^3.0.0",
-    "@testing-library/jest-native": "^5.3.3",
-    "@testing-library/react-native": "^11.5.0",
+    "@testing-library/jest-native": "^5.4.1",
+    "@testing-library/react-native": "^11.5.2",
     "@tsconfig/react-native": "^2.0.3",
     "@types/he": "^1.1.2",
-    "@types/jest": "^26.0.23",
+    "@types/jest": "^29.4.0",
     "@types/lodash.chunk": "^4.2.7",
     "@types/lodash.clonedeep": "^4.5.7",
     "@types/lodash.isequal": "^4.5.6",
@@ -109,7 +131,7 @@
     "@types/react-test-renderer": "^17.0.1",
     "@typescript-eslint/eslint-plugin": "^5.48.2",
     "@typescript-eslint/parser": "^5.48.2",
-    "babel-jest": "^29.2.1",
+    "babel-jest": "^29.4.2",
     "babel-loader": "^9.1.2",
     "babel-plugin-module-resolver": "^5.0.0",
     "babel-plugin-react-native-web": "^0.18.12",
@@ -119,6 +141,7 @@
     "eslint-plugin-ft-flow": "^2.0.3",
     "html-webpack-plugin": "^5.5.0",
     "jest": "^29.4.3",
+    "jest-expo": "^48.0.0-beta.2",
     "jest-junit": "^15.0.0",
     "metro-react-native-babel-preset": "^0.73.7",
     "prettier": "^2.8.3",
@@ -135,12 +158,9 @@
     "@types/react": "^17"
   },
   "jest": {
-    "preset": "react-native",
-    "setupFiles": [
-      "./jest/jestSetup.js",
-      "./node_modules/react-native-gesture-handler/jestSetup.js"
-    ],
+    "preset": "jest-expo/ios",
     "setupFilesAfterEnv": [
+      "./jest/jestSetup.js",
       "@testing-library/jest-native/extend-expect"
     ],
     "moduleFileExtensions": [
@@ -152,7 +172,7 @@
       "node"
     ],
     "transformIgnorePatterns": [
-      "node_modules/(?!(jest-)?react-native|react-clone-referenced-element|@react-native-community|rollbar-react-native|@fortawesome|@react-native|@react-navigation|normalize-url)"
+      "node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|normalize-url|react-native-svg)"
     ],
     "modulePathIgnorePatterns": [
       "__tests__/.*/__mocks__",
diff --git a/public/index.html b/public/index.html
deleted file mode 100644
index 575e0421d..000000000
--- a/public/index.html
+++ /dev/null
@@ -1,24 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-  <head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width">     
-    <title> WEBAPP </title>
-    <style>
-      /* These styles make the body full-height */
-      html, body { height: 100%; }
-      /* These styles disable body scrolling if you are using <ScrollView> */
-      body { overflow: hidden; }
-      /* These styles make the root element full-height */
-      #app-root { display:flex; height:100%; }
-
-      /* Remove focus state on inputs */
-      *:focus {
-        outline: 0;
-      }
-    </style>
-  </head>
-  <body>
-    <div id="app-root"></div>
-  </body>
-</html>
\ No newline at end of file
diff --git a/src/App.native.tsx b/src/App.native.tsx
index 8bb204923..fcd6e787b 100644
--- a/src/App.native.tsx
+++ b/src/App.native.tsx
@@ -2,18 +2,17 @@ import 'react-native-url-polyfill/auto'
 import React, {useState, useEffect} from 'react'
 import {Linking} from 'react-native'
 import {RootSiblingParent} from 'react-native-root-siblings'
-import {GestureHandlerRootView} from 'react-native-gesture-handler'
 import SplashScreen from 'react-native-splash-screen'
 import {SafeAreaProvider} from 'react-native-safe-area-context'
 import {observer} from 'mobx-react-lite'
 import {ThemeProvider} from 'lib/ThemeContext'
 import * as view from './view/index'
 import {RootStoreModel, setupState, RootStoreProvider} from './state'
-import {MobileShell} from './view/shell/mobile'
-import {s} from 'lib/styles'
+import {Shell} from './view/shell'
 import * as notifee from 'lib/notifee'
 import * as analytics from 'lib/analytics'
 import * as Toast from './view/com/util/Toast'
+import {handleLink} from './Navigation'
 
 const App = observer(() => {
   const [rootStore, setRootStore] = useState<RootStoreModel | undefined>(
@@ -31,11 +30,11 @@ const App = observer(() => {
       store.hackCheckIfUpgradeNeeded()
       Linking.getInitialURL().then((url: string | null) => {
         if (url) {
-          store.nav.handleLink(url)
+          handleLink(url)
         }
       })
       Linking.addEventListener('url', ({url}) => {
-        store.nav.handleLink(url)
+        handleLink(url)
       })
       store.onSessionDropped(() => {
         Toast.show('Sorry! Your session expired. Please log in again.')
@@ -48,19 +47,17 @@ const App = observer(() => {
     return null
   }
   return (
-    <GestureHandlerRootView style={s.h100pct}>
-      <ThemeProvider theme={rootStore.shell.darkMode ? 'dark' : 'light'}>
-        <RootSiblingParent>
-          <analytics.Provider>
-            <RootStoreProvider value={rootStore}>
-              <SafeAreaProvider>
-                <MobileShell />
-              </SafeAreaProvider>
-            </RootStoreProvider>
-          </analytics.Provider>
-        </RootSiblingParent>
-      </ThemeProvider>
-    </GestureHandlerRootView>
+    <ThemeProvider theme={rootStore.shell.darkMode ? 'dark' : 'light'}>
+      <RootSiblingParent>
+        <analytics.Provider>
+          <RootStoreProvider value={rootStore}>
+            <SafeAreaProvider>
+              <Shell />
+            </SafeAreaProvider>
+          </RootStoreProvider>
+        </analytics.Provider>
+      </RootSiblingParent>
+    </ThemeProvider>
   )
 })
 
diff --git a/src/App.web.tsx b/src/App.web.tsx
index 84d3b6cd6..0bfa909be 100644
--- a/src/App.web.tsx
+++ b/src/App.web.tsx
@@ -1,9 +1,9 @@
 import React, {useState, useEffect} from 'react'
 import {SafeAreaProvider} from 'react-native-safe-area-context'
-import {getInitialURL} from 'platform/urls'
+import {RootSiblingParent} from 'react-native-root-siblings'
 import * as view from './view/index'
 import {RootStoreModel, setupState, RootStoreProvider} from './state'
-import {WebShell} from './view/shell/web'
+import {Shell} from './view/shell/index'
 import {ToastContainer} from './view/com/util/Toast.web'
 
 function App() {
@@ -16,12 +16,6 @@ function App() {
     view.setup()
     setupState().then(store => {
       setRootStore(store)
-      store.nav.bindWebNavigation()
-      getInitialURL().then(url => {
-        if (url) {
-          store.nav.handleLink(url)
-        }
-      })
     })
   }, [])
 
@@ -31,12 +25,14 @@ function App() {
   }
 
   return (
-    <RootStoreProvider value={rootStore}>
-      <SafeAreaProvider>
-        <WebShell />
-      </SafeAreaProvider>
-      <ToastContainer />
-    </RootStoreProvider>
+    <RootSiblingParent>
+      <RootStoreProvider value={rootStore}>
+        <SafeAreaProvider>
+          <Shell />
+        </SafeAreaProvider>
+        <ToastContainer />
+      </RootStoreProvider>
+    </RootSiblingParent>
   )
 }
 
diff --git a/src/Navigation.tsx b/src/Navigation.tsx
new file mode 100644
index 000000000..22d8d8b21
--- /dev/null
+++ b/src/Navigation.tsx
@@ -0,0 +1,287 @@
+import * as React from 'react'
+import {StyleSheet} from 'react-native'
+import {
+  NavigationContainer,
+  createNavigationContainerRef,
+  StackActions,
+} from '@react-navigation/native'
+import {createNativeStackNavigator} from '@react-navigation/native-stack'
+import {createBottomTabNavigator} from '@react-navigation/bottom-tabs'
+import {
+  HomeTabNavigatorParams,
+  SearchTabNavigatorParams,
+  NotificationsTabNavigatorParams,
+  FlatNavigatorParams,
+  AllNavigatorParams,
+} from 'lib/routes/types'
+import {BottomBar} from './view/shell/BottomBar'
+import {buildStateObject} from 'lib/routes/helpers'
+import {State, RouteParams} from 'lib/routes/types'
+import {colors} from 'lib/styles'
+import {isNative} from 'platform/detection'
+import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle'
+import {router} from './routes'
+
+import {HomeScreen} from './view/screens/Home'
+import {SearchScreen} from './view/screens/Search'
+import {NotificationsScreen} from './view/screens/Notifications'
+import {NotFoundScreen} from './view/screens/NotFound'
+import {SettingsScreen} from './view/screens/Settings'
+import {ProfileScreen} from './view/screens/Profile'
+import {ProfileFollowersScreen} from './view/screens/ProfileFollowers'
+import {ProfileFollowsScreen} from './view/screens/ProfileFollows'
+import {PostThreadScreen} from './view/screens/PostThread'
+import {PostUpvotedByScreen} from './view/screens/PostUpvotedBy'
+import {PostRepostedByScreen} from './view/screens/PostRepostedBy'
+import {DebugScreen} from './view/screens/Debug'
+import {LogScreen} from './view/screens/Log'
+
+const navigationRef = createNavigationContainerRef<AllNavigatorParams>()
+
+const HomeTab = createNativeStackNavigator<HomeTabNavigatorParams>()
+const SearchTab = createNativeStackNavigator<SearchTabNavigatorParams>()
+const NotificationsTab =
+  createNativeStackNavigator<NotificationsTabNavigatorParams>()
+const Flat = createNativeStackNavigator<FlatNavigatorParams>()
+const Tab = createBottomTabNavigator()
+
+/**
+ * These "common screens" are reused across stacks.
+ */
+function commonScreens(Stack: typeof HomeTab) {
+  return (
+    <>
+      <Stack.Screen name="NotFound" component={NotFoundScreen} />
+      <Stack.Screen name="Settings" component={SettingsScreen} />
+      <Stack.Screen name="Profile" component={ProfileScreen} />
+      <Stack.Screen
+        name="ProfileFollowers"
+        component={ProfileFollowersScreen}
+      />
+      <Stack.Screen name="ProfileFollows" component={ProfileFollowsScreen} />
+      <Stack.Screen name="PostThread" component={PostThreadScreen} />
+      <Stack.Screen name="PostUpvotedBy" component={PostUpvotedByScreen} />
+      <Stack.Screen name="PostRepostedBy" component={PostRepostedByScreen} />
+      <Stack.Screen name="Debug" component={DebugScreen} />
+      <Stack.Screen name="Log" component={LogScreen} />
+    </>
+  )
+}
+
+/**
+ * The TabsNavigator is used by native mobile to represent the routes
+ * in 3 distinct tab-stacks with a different root screen on each.
+ */
+function TabsNavigator() {
+  const tabBar = React.useCallback(props => <BottomBar {...props} />, [])
+  return (
+    <Tab.Navigator
+      initialRouteName="HomeTab"
+      backBehavior="initialRoute"
+      screenOptions={{headerShown: false}}
+      tabBar={tabBar}>
+      <Tab.Screen name="HomeTab" component={HomeTabNavigator} />
+      <Tab.Screen
+        name="NotificationsTab"
+        component={NotificationsTabNavigator}
+      />
+      <Tab.Screen name="SearchTab" component={SearchTabNavigator} />
+    </Tab.Navigator>
+  )
+}
+
+function HomeTabNavigator() {
+  const contentStyle = useColorSchemeStyle(styles.bgLight, styles.bgDark)
+  return (
+    <HomeTab.Navigator
+      screenOptions={{
+        gestureEnabled: true,
+        fullScreenGestureEnabled: true,
+        headerShown: false,
+        animationDuration: 250,
+        contentStyle,
+      }}>
+      <HomeTab.Screen name="Home" component={HomeScreen} />
+      {commonScreens(HomeTab)}
+    </HomeTab.Navigator>
+  )
+}
+
+function SearchTabNavigator() {
+  const contentStyle = useColorSchemeStyle(styles.bgLight, styles.bgDark)
+  return (
+    <SearchTab.Navigator
+      screenOptions={{
+        gestureEnabled: true,
+        fullScreenGestureEnabled: true,
+        headerShown: false,
+        animationDuration: 250,
+        contentStyle,
+      }}>
+      <SearchTab.Screen name="Search" component={SearchScreen} />
+      {commonScreens(SearchTab as typeof HomeTab)}
+    </SearchTab.Navigator>
+  )
+}
+
+function NotificationsTabNavigator() {
+  const contentStyle = useColorSchemeStyle(styles.bgLight, styles.bgDark)
+  return (
+    <NotificationsTab.Navigator
+      screenOptions={{
+        gestureEnabled: true,
+        fullScreenGestureEnabled: true,
+        headerShown: false,
+        animationDuration: 250,
+        contentStyle,
+      }}>
+      <NotificationsTab.Screen
+        name="Notifications"
+        component={NotificationsScreen}
+      />
+      {commonScreens(NotificationsTab as typeof HomeTab)}
+    </NotificationsTab.Navigator>
+  )
+}
+
+/**
+ * The FlatNavigator is used by Web to represent the routes
+ * in a single ("flat") stack.
+ */
+function FlatNavigator() {
+  return (
+    <Flat.Navigator
+      screenOptions={{
+        gestureEnabled: true,
+        fullScreenGestureEnabled: true,
+        headerShown: false,
+        animationDuration: 250,
+        contentStyle: {backgroundColor: 'white'},
+      }}>
+      <Flat.Screen name="Home" component={HomeScreen} />
+      <Flat.Screen name="Search" component={SearchScreen} />
+      <Flat.Screen name="Notifications" component={NotificationsScreen} />
+      {commonScreens(Flat as typeof HomeTab)}
+    </Flat.Navigator>
+  )
+}
+
+/**
+ * The RoutesContainer should wrap all components which need access
+ * to the navigation context.
+ */
+
+const LINKING = {
+  prefixes: ['bsky://', 'https://bsky.app'],
+
+  getPathFromState(state: State) {
+    // find the current node in the navigation tree
+    let node = state.routes[state.index || 0]
+    while (node.state?.routes && typeof node.state?.index === 'number') {
+      node = node.state?.routes[node.state?.index]
+    }
+
+    // build the path
+    const route = router.matchName(node.name)
+    if (typeof route === 'undefined') {
+      return '/' // default to home
+    }
+    return route.build((node.params || {}) as RouteParams)
+  },
+
+  getStateFromPath(path: string) {
+    const [name, params] = router.matchPath(path)
+    if (isNative) {
+      if (name === 'Search') {
+        return buildStateObject('SearchTab', 'Search', params)
+      }
+      if (name === 'Notifications') {
+        return buildStateObject('NotificationsTab', 'Notifications', params)
+      }
+      return buildStateObject('HomeTab', name, params)
+    } else {
+      return buildStateObject('Flat', name, params)
+    }
+  },
+}
+
+function RoutesContainer({children}: React.PropsWithChildren<{}>) {
+  return (
+    <NavigationContainer ref={navigationRef} linking={LINKING}>
+      {children}
+    </NavigationContainer>
+  )
+}
+
+/**
+ * These helpers can be used from outside of the RoutesContainer
+ * (eg in the state models).
+ */
+
+function navigate<K extends keyof AllNavigatorParams>(
+  name: K,
+  params?: AllNavigatorParams[K],
+) {
+  if (navigationRef.isReady()) {
+    // @ts-ignore I dont know what would make typescript happy but I have a life -prf
+    navigationRef.navigate(name, params)
+  }
+}
+
+function resetToTab(tabName: 'HomeTab' | 'SearchTab' | 'NotificationsTab') {
+  if (navigationRef.isReady()) {
+    navigate(tabName)
+    navigationRef.dispatch(StackActions.popToTop())
+  }
+}
+
+function handleLink(url: string) {
+  let path
+  if (url.startsWith('/')) {
+    path = url
+  } else if (url.startsWith('http')) {
+    try {
+      path = new URL(url).pathname
+    } catch (e) {
+      console.error('Invalid url', url, e)
+      return
+    }
+  } else {
+    console.error('Invalid url', url)
+    return
+  }
+
+  const [name, params] = router.matchPath(path)
+  if (isNative) {
+    if (name === 'Search') {
+      resetToTab('SearchTab')
+    } else if (name === 'Notifications') {
+      resetToTab('NotificationsTab')
+    } else {
+      resetToTab('HomeTab')
+      // @ts-ignore matchPath doesnt give us type-checked output -prf
+      navigate(name, params)
+    }
+  } else {
+    // @ts-ignore matchPath doesnt give us type-checked output -prf
+    navigate(name, params)
+  }
+}
+
+const styles = StyleSheet.create({
+  bgDark: {
+    backgroundColor: colors.black,
+  },
+  bgLight: {
+    backgroundColor: colors.gray1,
+  },
+})
+
+export {
+  navigate,
+  resetToTab,
+  handleLink,
+  TabsNavigator,
+  FlatNavigator,
+  RoutesContainer,
+}
diff --git a/src/app.json b/src/app.json
deleted file mode 100644
index 1f9e391e0..000000000
--- a/src/app.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-  "name": "xyz.blueskyweb.app",
-  "displayName": "Bluesky"
-}
\ No newline at end of file
diff --git a/src/index.js b/src/index.js
deleted file mode 100644
index 45a06f40a..000000000
--- a/src/index.js
+++ /dev/null
@@ -1,12 +0,0 @@
-/**
- * @format
- */
-
-import {AppRegistry} from 'react-native'
-import App from './App'
-
-AppRegistry.registerComponent('App', () => App)
-
-AppRegistry.runApplication('App', {
-  rootTag: document.getElementById('root'),
-})
diff --git a/src/lib/analytics.tsx b/src/lib/analytics.tsx
index 5358a8682..725dd2328 100644
--- a/src/lib/analytics.tsx
+++ b/src/lib/analytics.tsx
@@ -16,7 +16,7 @@ export function init(store: RootStoreModel) {
   // this method is a copy of segment's own lifecycle event tracking
   // we handle it manually to ensure that it never fires while the app is backgrounded
   // -prf
-  segmentClient.onContextLoaded(() => {
+  segmentClient.isReady.onChange(() => {
     if (AppState.currentState !== 'active') {
       store.log.debug('Prevented a metrics ping while the app was backgrounded')
       return
diff --git a/src/lib/api/index.ts b/src/lib/api/index.ts
index 3b8af44e8..85eca4a61 100644
--- a/src/lib/api/index.ts
+++ b/src/lib/api/index.ts
@@ -117,7 +117,9 @@ export async function post(store: RootStoreModel, opts: PostOpts) {
     if (opts.extLink.localThumb) {
       opts.onStateChange?.('Uploading link thumbnail...')
       let encoding
-      if (opts.extLink.localThumb.path.endsWith('.png')) {
+      if (opts.extLink.localThumb.mime) {
+        encoding = opts.extLink.localThumb.mime
+      } else if (opts.extLink.localThumb.path.endsWith('.png')) {
         encoding = 'image/png'
       } else if (
         opts.extLink.localThumb.path.endsWith('.jpeg') ||
diff --git a/src/lib/assets.native.ts b/src/lib/assets.native.ts
index d7f4a7287..d7ef9a05e 100644
--- a/src/lib/assets.native.ts
+++ b/src/lib/assets.native.ts
@@ -1,5 +1,5 @@
 import {ImageRequireSource} from 'react-native'
 
-export const DEF_AVATAR: ImageRequireSource = require('../../public/img/default-avatar.jpg')
-export const TABS_EXPLAINER: ImageRequireSource = require('../../public/img/tabs-explainer.jpg')
-export const CLOUD_SPLASH: ImageRequireSource = require('../../public/img/cloud-splash.png')
+export const DEF_AVATAR: ImageRequireSource = require('../../assets/default-avatar.jpg')
+export const TABS_EXPLAINER: ImageRequireSource = require('../../assets/tabs-explainer.jpg')
+export const CLOUD_SPLASH: ImageRequireSource = require('../../assets/cloud-splash.png')
diff --git a/src/lib/build-flags.ts b/src/lib/build-flags.ts
index 155230e5d..28b650b6f 100644
--- a/src/lib/build-flags.ts
+++ b/src/lib/build-flags.ts
@@ -1,2 +1 @@
 export const LOGIN_INCLUDE_DEV_SERVERS = true
-export const TABS_ENABLED = false
diff --git a/src/lib/constants.ts b/src/lib/constants.ts
index 31947cd8f..ef4bb0f08 100644
--- a/src/lib/constants.ts
+++ b/src/lib/constants.ts
@@ -166,5 +166,3 @@ export function SUGGESTED_FOLLOWS(serviceUrl: string) {
 export const POST_IMG_MAX_WIDTH = 2000
 export const POST_IMG_MAX_HEIGHT = 2000
 export const POST_IMG_MAX_SIZE = 1000000
-
-export const DESKTOP_HEADER_HEIGHT = 57
diff --git a/src/lib/hooks/useColorSchemeStyle.ts b/src/lib/hooks/useColorSchemeStyle.ts
index 61e3d7cc9..18c48b961 100644
--- a/src/lib/hooks/useColorSchemeStyle.ts
+++ b/src/lib/hooks/useColorSchemeStyle.ts
@@ -1,6 +1,6 @@
-import {useColorScheme} from 'react-native'
+import {useTheme} from 'lib/ThemeContext'
 
 export function useColorSchemeStyle(lightStyle: any, darkStyle: any) {
-  const colorScheme = useColorScheme()
+  const colorScheme = useTheme().colorScheme
   return colorScheme === 'dark' ? darkStyle : lightStyle
 }
diff --git a/src/lib/hooks/usePermissions.ts b/src/lib/hooks/usePermissions.ts
new file mode 100644
index 000000000..36a92ac32
--- /dev/null
+++ b/src/lib/hooks/usePermissions.ts
@@ -0,0 +1,50 @@
+import {Alert} from 'react-native'
+import {Camera} from 'expo-camera'
+import * as MediaLibrary from 'expo-media-library'
+import {Linking} from 'react-native'
+
+const openSettings = () => {
+  Linking.openURL('app-settings:')
+}
+
+const openPermissionAlert = (perm: string) => {
+  Alert.alert(
+    'Permission needed',
+    `Bluesky does not have permission to access your ${perm}.`,
+    [
+      {
+        text: 'Cancel',
+        style: 'cancel',
+      },
+      {text: 'Open Settings', onPress: () => openSettings()},
+    ],
+  )
+}
+
+export function usePhotoLibraryPermission() {
+  const [mediaLibraryPermissions] = MediaLibrary.usePermissions()
+  const requestPhotoAccessIfNeeded = async () => {
+    if (mediaLibraryPermissions?.status === 'granted') {
+      return true
+    } else {
+      openPermissionAlert('photo library')
+      return false
+    }
+  }
+  return {requestPhotoAccessIfNeeded}
+}
+
+export function useCameraPermission() {
+  const [cameraPermissionStatus] = Camera.useCameraPermissions()
+
+  const requestCameraAccessIfNeeded = async () => {
+    if (cameraPermissionStatus?.granted) {
+      return true
+    } else {
+      openPermissionAlert('camera')
+      return false
+    }
+  }
+
+  return {requestCameraAccessIfNeeded}
+}
diff --git a/src/lib/icons.tsx b/src/lib/icons.tsx
index f82ea2602..e194e7a87 100644
--- a/src/lib/icons.tsx
+++ b/src/lib/icons.tsx
@@ -73,12 +73,10 @@ export function HomeIconSolid({
   style,
   size,
   strokeWidth = 4,
-  fillOpacity = 1,
 }: {
   style?: StyleProp<ViewStyle>
   size?: string | number
   strokeWidth?: number
-  fillOpacity?: number
 }) {
   return (
     <Svg
@@ -89,11 +87,6 @@ export function HomeIconSolid({
       style={style}>
       <Path
         fill="currentColor"
-        stroke="none"
-        opacity={fillOpacity}
-        d="M 23.951 2 C 23.631 2.011 23.323 2.124 23.072 2.322 L 8.859 13.52 C 7.055 14.941 6 17.114 6 19.41 L 6 38.5 C 6 39.864 7.136 41 8.5 41 L 18.5 41 C 19.864 41 21 39.864 21 38.5 L 21 28.5 C 21 28.205 21.205 28 21.5 28 L 26.5 28 C 26.795 28 27 28.205 27 28.5 L 27 38.5 C 27 39.864 28.136 41 29.5 41 L 39.5 41 C 40.864 41 42 39.864 42 38.5 L 42 19.41 C 42 17.114 40.945 14.941 39.141 13.52 L 24.928 2.322 C 24.65 2.103 24.304 1.989 23.951 2 Z"
-      />
-      <Path
         strokeWidth={strokeWidth}
         d="M 23.951 2 C 23.631 2.011 23.323 2.124 23.072 2.322 L 8.859 13.52 C 7.055 14.941 6 17.114 6 19.41 L 6 38.5 C 6 39.864 7.136 41 8.5 41 L 18.5 41 C 19.864 41 21 39.864 21 38.5 L 21 28.5 C 21 28.205 21.205 28 21.5 28 L 26.5 28 C 26.795 28 27 28.205 27 28.5 L 27 38.5 C 27 39.864 28.136 41 29.5 41 L 39.5 41 C 40.864 41 42 39.864 42 38.5 L 42 19.41 C 42 17.114 40.945 14.941 39.141 13.52 L 24.928 2.322 C 24.65 2.103 24.304 1.989 23.951 2 Z"
       />
@@ -158,12 +151,10 @@ export function MagnifyingGlassIcon2Solid({
   style,
   size,
   strokeWidth = 2,
-  fillOpacity = 1,
 }: {
   style?: StyleProp<ViewStyle>
   size?: string | number
   strokeWidth?: number
-  fillOpacity?: number
 }) {
   return (
     <Svg
@@ -181,7 +172,6 @@ export function MagnifyingGlassIcon2Solid({
         ry="7"
         stroke="none"
         fill="currentColor"
-        opacity={fillOpacity}
       />
       <Ellipse cx="12" cy="11" rx="9" ry="9" />
       <Line x1="19" y1="17.3" x2="23.5" y2="21" strokeLinecap="round" />
@@ -219,12 +209,10 @@ export function BellIconSolid({
   style,
   size,
   strokeWidth = 1.5,
-  fillOpacity = 1,
 }: {
   style?: StyleProp<ViewStyle>
   size?: string | number
   strokeWidth?: number
-  fillOpacity?: number
 }) {
   return (
     <Svg
@@ -237,10 +225,7 @@ export function BellIconSolid({
       <Path
         d="M 11.642 2 H 12.442 A 8.6 8.55 0 0 1 21.042 10.55 V 18.1 A 1 1 0 0 1 20.042 19.1 H 4.042 A 1 1 0 0 1 3.042 18.1 V 10.55 A 8.6 8.55 0 0 1 11.642 2 Z"
         fill="currentColor"
-        stroke="none"
-        opacity={fillOpacity}
       />
-      <Path d="M 11.642 2 H 12.442 A 8.6 8.55 0 0 1 21.042 10.55 V 18.1 A 1 1 0 0 1 20.042 19.1 H 4.042 A 1 1 0 0 1 3.042 18.1 V 10.55 A 8.6 8.55 0 0 1 11.642 2 Z" />
       <Line x1="9" y1="22" x2="15" y2="22" />
     </Svg>
   )
@@ -278,6 +263,34 @@ export function CogIcon({
   )
 }
 
+export function CogIconSolid({
+  style,
+  size,
+  strokeWidth = 1.5,
+}: {
+  style?: StyleProp<ViewStyle>
+  size?: string | number
+  strokeWidth: number
+}) {
+  return (
+    <Svg
+      fill="none"
+      viewBox="0 0 24 24"
+      width={size || 32}
+      height={size || 32}
+      strokeWidth={strokeWidth}
+      stroke="currentColor"
+      style={style}>
+      <Path
+        strokeLinecap="round"
+        strokeLinejoin="round"
+        d="M 9.594 3.94 C 9.684 3.398 10.154 3 10.704 3 L 13.297 3 C 13.847 3 14.317 3.398 14.407 3.94 L 14.62 5.221 C 14.683 5.595 14.933 5.907 15.265 6.091 C 15.339 6.131 15.412 6.174 15.485 6.218 C 15.809 6.414 16.205 6.475 16.56 6.342 L 17.777 5.886 C 18.292 5.692 18.872 5.9 19.147 6.376 L 20.443 8.623 C 20.718 9.099 20.608 9.705 20.183 10.054 L 19.18 10.881 C 18.887 11.121 18.742 11.494 18.749 11.873 C 18.751 11.958 18.751 12.043 18.749 12.128 C 18.742 12.506 18.887 12.878 19.179 13.118 L 20.184 13.946 C 20.608 14.296 20.718 14.9 20.444 15.376 L 19.146 17.623 C 18.871 18.099 18.292 18.307 17.777 18.114 L 16.56 17.658 C 16.205 17.525 15.81 17.586 15.484 17.782 C 15.412 17.826 15.338 17.869 15.264 17.91 C 14.933 18.093 14.683 18.405 14.62 18.779 L 14.407 20.059 C 14.317 20.602 13.847 21 13.297 21 L 10.703 21 C 10.153 21 9.683 20.602 9.593 20.06 L 9.38 18.779 C 9.318 18.405 9.068 18.093 8.736 17.909 C 8.662 17.868 8.589 17.826 8.516 17.782 C 8.191 17.586 7.796 17.525 7.44 17.658 L 6.223 18.114 C 5.708 18.307 5.129 18.1 4.854 17.624 L 3.557 15.377 C 3.282 14.901 3.392 14.295 3.817 13.946 L 4.821 13.119 C 5.113 12.879 5.258 12.506 5.251 12.127 C 5.249 12.042 5.249 11.957 5.251 11.872 C 5.258 11.494 5.113 11.122 4.821 10.882 L 3.817 10.054 C 3.393 9.705 3.283 9.1 3.557 8.624 L 4.854 6.377 C 5.129 5.9 5.709 5.692 6.224 5.886 L 7.44 6.342 C 7.796 6.475 8.191 6.414 8.516 6.218 C 8.588 6.174 8.662 6.131 8.736 6.09 C 9.068 5.907 9.318 5.595 9.38 5.221 Z M 13.5 9.402 C 11.5 8.247 9 9.691 9 12 C 9 13.072 9.572 14.062 10.5 14.598 C 12.5 15.753 15 14.309 15 12 C 15 10.928 14.428 9.938 13.5 9.402 Z"
+        fill="currentColor"
+      />
+    </Svg>
+  )
+}
+
 // Copyright (c) 2020 Refactoring UI Inc.
 // https://github.com/tailwindlabs/heroicons/blob/master/LICENSE
 export function MoonIcon({
@@ -336,6 +349,45 @@ export function UserIcon({
   )
 }
 
+export function UserIconSolid({
+  style,
+  size,
+  strokeWidth = 1.5,
+}: {
+  style?: StyleProp<ViewStyle>
+  size?: string | number
+  strokeWidth?: number
+}) {
+  return (
+    <Svg
+      fill="none"
+      viewBox="0 0 24 24"
+      width={size || 32}
+      height={size || 32}
+      strokeWidth={strokeWidth}
+      stroke="currentColor"
+      style={style}>
+      <Path
+        strokeLinecap="round"
+        strokeLinejoin="round"
+        fill="currentColor"
+        d="M 15 9.75 C 15 12.059 12.5 13.503 10.5 12.348 C 9.572 11.812 9 10.822 9 9.75 C 9 7.441 11.5 5.997 13.5 7.152 C 14.428 7.688 15 8.678 15 9.75 Z"
+      />
+      <Path
+        strokeLinecap="round"
+        strokeLinejoin="round"
+        fill="currentColor"
+        d="M 17.982 18.725 C 16.565 16.849 14.35 15.748 12 15.75 C 9.65 15.748 7.435 16.849 6.018 18.725 M 17.981 18.725 C 16.335 20.193 14.206 21.003 12 21 C 9.794 21.003 7.664 20.193 6.018 18.725"
+      />
+      <Path
+        strokeLinecap="round"
+        strokeLinejoin="round"
+        d="M 17.981 18.725 C 23.158 14.12 21.409 5.639 14.833 3.458 C 8.257 1.277 1.786 7.033 3.185 13.818 C 3.576 15.716 4.57 17.437 6.018 18.725 M 17.981 18.725 C 16.335 20.193 14.206 21.003 12 21 C 9.794 21.003 7.664 20.193 6.018 18.725"
+      />
+    </Svg>
+  )
+}
+
 // Copyright (c) 2020 Refactoring UI Inc.
 // https://github.com/tailwindlabs/heroicons/blob/master/LICENSE
 export function UserGroupIcon({
@@ -674,6 +726,7 @@ export function ComposeIcon2({
     <Svg
       viewBox="0 0 24 24"
       stroke="currentColor"
+      fill="none"
       width={size || 24}
       height={size || 24}
       style={style}>
diff --git a/src/lib/link-meta/bsky.ts b/src/lib/link-meta/bsky.ts
index c9c2ed31a..0d8e8c69b 100644
--- a/src/lib/link-meta/bsky.ts
+++ b/src/lib/link-meta/bsky.ts
@@ -1,19 +1,20 @@
 import {LikelyType, LinkMeta} from './link-meta'
-import {match as matchRoute} from 'view/routes'
+// import {match as matchRoute} from 'view/routes'
 import {convertBskyAppUrlIfNeeded, makeRecordUri} from '../strings/url-helpers'
 import {RootStoreModel} from 'state/index'
 import {PostThreadViewModel} from 'state/models/post-thread-view'
 import {ComposerOptsQuote} from 'state/models/shell-ui'
 
-import {Home} from 'view/screens/Home'
-import {Search} from 'view/screens/Search'
-import {Notifications} from 'view/screens/Notifications'
-import {PostThread} from 'view/screens/PostThread'
-import {PostUpvotedBy} from 'view/screens/PostUpvotedBy'
-import {PostRepostedBy} from 'view/screens/PostRepostedBy'
-import {Profile} from 'view/screens/Profile'
-import {ProfileFollowers} from 'view/screens/ProfileFollowers'
-import {ProfileFollows} from 'view/screens/ProfileFollows'
+// TODO
+// import {Home} from 'view/screens/Home'
+// import {Search} from 'view/screens/Search'
+// import {Notifications} from 'view/screens/Notifications'
+// import {PostThread} from 'view/screens/PostThread'
+// import {PostUpvotedBy} from 'view/screens/PostUpvotedBy'
+// import {PostRepostedBy} from 'view/screens/PostRepostedBy'
+// import {Profile} from 'view/screens/Profile'
+// import {ProfileFollowers} from 'view/screens/ProfileFollowers'
+// import {ProfileFollows} from 'view/screens/ProfileFollows'
 
 // NOTE
 // this is a hack around the lack of hosted social metadata
@@ -24,77 +25,77 @@ export async function extractBskyMeta(
   url: string,
 ): Promise<LinkMeta> {
   url = convertBskyAppUrlIfNeeded(url)
-  const route = matchRoute(url)
+  // const route = matchRoute(url)
   let meta: LinkMeta = {
     likelyType: LikelyType.AtpData,
     url,
-    title: route.defaultTitle,
+    // title: route.defaultTitle,
   }
 
-  if (route.Com === Home) {
-    meta = {
-      ...meta,
-      title: 'Bluesky',
-      description: 'A new kind of social network',
-    }
-  } else if (route.Com === Search) {
-    meta = {
-      ...meta,
-      title: 'Search - Bluesky',
-      description: 'A new kind of social network',
-    }
-  } else if (route.Com === Notifications) {
-    meta = {
-      ...meta,
-      title: 'Notifications - Bluesky',
-      description: 'A new kind of social network',
-    }
-  } else if (
-    route.Com === PostThread ||
-    route.Com === PostUpvotedBy ||
-    route.Com === PostRepostedBy
-  ) {
-    // post and post-related screens
-    const threadUri = makeRecordUri(
-      route.params.name,
-      'app.bsky.feed.post',
-      route.params.rkey,
-    )
-    const threadView = new PostThreadViewModel(store, {
-      uri: threadUri,
-      depth: 0,
-    })
-    await threadView.setup().catch(_err => undefined)
-    const title = [
-      route.Com === PostUpvotedBy
-        ? 'Likes on a post by'
-        : route.Com === PostRepostedBy
-        ? 'Reposts of a post by'
-        : 'Post by',
-      threadView.thread?.post.author.displayName ||
-        threadView.thread?.post.author.handle ||
-        'a bluesky user',
-    ].join(' ')
-    meta = {
-      ...meta,
-      title,
-      description: threadView.thread?.postRecord?.text,
-    }
-  } else if (
-    route.Com === Profile ||
-    route.Com === ProfileFollowers ||
-    route.Com === ProfileFollows
-  ) {
-    // profile and profile-related screens
-    const profile = await store.profiles.getProfile(route.params.name)
-    if (profile?.data) {
-      meta = {
-        ...meta,
-        title: profile.data.displayName || profile.data.handle,
-        description: profile.data.description,
-      }
-    }
-  }
+  // if (route.Com === Home) {
+  //   meta = {
+  //     ...meta,
+  //     title: 'Bluesky',
+  //     description: 'A new kind of social network',
+  //   }
+  // } else if (route.Com === Search) {
+  //   meta = {
+  //     ...meta,
+  //     title: 'Search - Bluesky',
+  //     description: 'A new kind of social network',
+  //   }
+  // } else if (route.Com === Notifications) {
+  //   meta = {
+  //     ...meta,
+  //     title: 'Notifications - Bluesky',
+  //     description: 'A new kind of social network',
+  //   }
+  // } else if (
+  //   route.Com === PostThread ||
+  //   route.Com === PostUpvotedBy ||
+  //   route.Com === PostRepostedBy
+  // ) {
+  //   // post and post-related screens
+  //   const threadUri = makeRecordUri(
+  //     route.params.name,
+  //     'app.bsky.feed.post',
+  //     route.params.rkey,
+  //   )
+  //   const threadView = new PostThreadViewModel(store, {
+  //     uri: threadUri,
+  //     depth: 0,
+  //   })
+  //   await threadView.setup().catch(_err => undefined)
+  //   const title = [
+  //     route.Com === PostUpvotedBy
+  //       ? 'Likes on a post by'
+  //       : route.Com === PostRepostedBy
+  //       ? 'Reposts of a post by'
+  //       : 'Post by',
+  //     threadView.thread?.post.author.displayName ||
+  //       threadView.thread?.post.author.handle ||
+  //       'a bluesky user',
+  //   ].join(' ')
+  //   meta = {
+  //     ...meta,
+  //     title,
+  //     description: threadView.thread?.postRecord?.text,
+  //   }
+  // } else if (
+  //   route.Com === Profile ||
+  //   route.Com === ProfileFollowers ||
+  //   route.Com === ProfileFollows
+  // ) {
+  //   // profile and profile-related screens
+  //   const profile = await store.profiles.getProfile(route.params.name)
+  //   if (profile?.data) {
+  //     meta = {
+  //       ...meta,
+  //       title: profile.data.displayName || profile.data.handle,
+  //       description: profile.data.description,
+  //     }
+  //   }
+  // }
 
   return meta
 }
diff --git a/src/lib/media/manip.web.ts b/src/lib/media/manip.web.ts
index e617d01af..cd0bb3bc9 100644
--- a/src/lib/media/manip.web.ts
+++ b/src/lib/media/manip.web.ts
@@ -1,5 +1,6 @@
 // import {Share} from 'react-native'
 // import * as Toast from 'view/com/util/Toast'
+import {extractDataUriMime, getDataUriSize} from './util'
 
 export interface DownloadAndResizeOpts {
   uri: string
@@ -18,9 +19,15 @@ export interface Image {
   height: number
 }
 
-export async function downloadAndResize(_opts: DownloadAndResizeOpts) {
-  // TODO
-  throw new Error('TODO')
+export async function downloadAndResize(opts: DownloadAndResizeOpts) {
+  const controller = new AbortController()
+  const to = setTimeout(() => controller.abort(), opts.timeout || 5e3)
+  const res = await fetch(opts.uri)
+  const resBody = await res.blob()
+  clearTimeout(to)
+
+  const dataUri = await blobToDataUri(resBody)
+  return await resize(dataUri, opts)
 }
 
 export interface ResizeOpts {
@@ -31,11 +38,18 @@ export interface ResizeOpts {
 }
 
 export async function resize(
-  _localUri: string,
+  dataUri: string,
   _opts: ResizeOpts,
 ): Promise<Image> {
-  // TODO
-  throw new Error('TODO')
+  const dim = await getImageDim(dataUri)
+  // TODO -- need to resize
+  return {
+    path: dataUri,
+    mime: extractDataUriMime(dataUri),
+    size: getDataUriSize(dataUri),
+    width: dim.width,
+    height: dim.height,
+  }
 }
 
 export async function compressIfNeeded(
@@ -86,3 +100,18 @@ export async function getImageDim(path: string): Promise<Dim> {
   await promise
   return {width: img.width, height: img.height}
 }
+
+function blobToDataUri(blob: Blob): Promise<string> {
+  return new Promise((resolve, reject) => {
+    const reader = new FileReader()
+    reader.onloadend = () => {
+      if (typeof reader.result === 'string') {
+        resolve(reader.result)
+      } else {
+        reject(new Error('Failed to read blob'))
+      }
+    }
+    reader.onerror = reject
+    reader.readAsDataURL(blob)
+  })
+}
diff --git a/src/lib/media/picker.web.tsx b/src/lib/media/picker.web.tsx
index 746feaedd..43675074e 100644
--- a/src/lib/media/picker.web.tsx
+++ b/src/lib/media/picker.web.tsx
@@ -10,6 +10,7 @@ import {
   compressIfNeeded,
   moveToPremanantPath,
 } from 'lib/media/manip'
+import {extractDataUriMime} from './util'
 
 interface PickedFile {
   uri: string
@@ -138,7 +139,3 @@ function selectFile(opts: PickerOpts): Promise<PickedFile> {
     input.click()
   })
 }
-
-function extractDataUriMime(uri: string): string {
-  return uri.substring(uri.indexOf(':') + 1, uri.indexOf(';'))
-}
diff --git a/src/lib/media/util.ts b/src/lib/media/util.ts
new file mode 100644
index 000000000..a27c71d82
--- /dev/null
+++ b/src/lib/media/util.ts
@@ -0,0 +1,7 @@
+export function extractDataUriMime(uri: string): string {
+  return uri.substring(uri.indexOf(':') + 1, uri.indexOf(';'))
+}
+
+export function getDataUriSize(uri: string): number {
+  return Math.round((uri.length * 3) / 4) // very rough estimate
+}
diff --git a/src/lib/notifee.ts b/src/lib/notifee.ts
index fb0afdd60..4baf64050 100644
--- a/src/lib/notifee.ts
+++ b/src/lib/notifee.ts
@@ -1,9 +1,9 @@
 import notifee, {EventType} from '@notifee/react-native'
 import {AppBskyEmbedImages} from '@atproto/api'
 import {RootStoreModel} from 'state/models/root-store'
-import {TabPurpose} from 'state/models/navigation'
 import {NotificationsViewItemModel} from 'state/models/notifications-view'
 import {enforceLen} from 'lib/strings/helpers'
+import {resetToTab} from '../Navigation'
 
 export function init(store: RootStoreModel) {
   store.onUnreadNotifications(count => notifee.setBadgeCount(count))
@@ -16,7 +16,7 @@ export function init(store: RootStoreModel) {
     store.log.debug('Notifee foreground event', {type})
     if (type === EventType.PRESS) {
       store.log.debug('User pressed a notifee, opening notifications')
-      store.nav.switchTo(TabPurpose.Notifs, true)
+      resetToTab('NotificationsTab')
     }
   })
   notifee.onBackgroundEvent(async _e => {}) // notifee requires this but we handle it with onForegroundEvent
diff --git a/src/lib/permissions.ts b/src/lib/permissions.ts
deleted file mode 100644
index ab2c73ca6..000000000
--- a/src/lib/permissions.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-import {Alert} from 'react-native'
-import {
-  check,
-  openSettings,
-  Permission,
-  PermissionStatus,
-  PERMISSIONS,
-  RESULTS,
-} from 'react-native-permissions'
-
-export const PHOTO_LIBRARY = PERMISSIONS.IOS.PHOTO_LIBRARY
-export const CAMERA = PERMISSIONS.IOS.CAMERA
-
-/**
- * Returns `true` if the user has granted permission or hasn't made
- * a decision yet. Returns `false` if unavailable or not granted.
- */
-export async function hasAccess(perm: Permission): Promise<boolean> {
-  const status = await check(perm)
-  return isntANo(status)
-}
-
-export async function requestAccessIfNeeded(
-  perm: Permission,
-): Promise<boolean> {
-  if (await hasAccess(perm)) {
-    return true
-  }
-  let permDescription
-  if (perm === PHOTO_LIBRARY) {
-    permDescription = 'photo library'
-  } else if (perm === CAMERA) {
-    permDescription = 'camera'
-  } else {
-    return false
-  }
-  Alert.alert(
-    'Permission needed',
-    `Bluesky does not have permission to access your ${permDescription}.`,
-    [
-      {
-        text: 'Cancel',
-        style: 'cancel',
-      },
-      {text: 'Open Settings', onPress: () => openSettings()},
-    ],
-  )
-  return false
-}
-
-export async function requestPhotoAccessIfNeeded() {
-  return requestAccessIfNeeded(PHOTO_LIBRARY)
-}
-
-export async function requestCameraAccessIfNeeded() {
-  return requestAccessIfNeeded(CAMERA)
-}
-
-function isntANo(status: PermissionStatus): boolean {
-  return status !== RESULTS.UNAVAILABLE && status !== RESULTS.BLOCKED
-}
diff --git a/src/lib/permissions.web.ts b/src/lib/permissions.web.ts
deleted file mode 100644
index 5b69637ed..000000000
--- a/src/lib/permissions.web.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
-At the moment, Web doesn't have any equivalence for these.
-*/
-
-export const PHOTO_LIBRARY = ''
-export const CAMERA = ''
-
-export async function hasAccess(_perm: any): Promise<boolean> {
-  return true
-}
-
-export async function requestAccessIfNeeded(_perm: any): Promise<boolean> {
-  return true
-}
-
-export async function requestPhotoAccessIfNeeded() {
-  return requestAccessIfNeeded(PHOTO_LIBRARY)
-}
-
-export async function requestCameraAccessIfNeeded() {
-  return requestAccessIfNeeded(CAMERA)
-}
diff --git a/src/lib/routes/helpers.ts b/src/lib/routes/helpers.ts
new file mode 100644
index 000000000..be76b9669
--- /dev/null
+++ b/src/lib/routes/helpers.ts
@@ -0,0 +1,77 @@
+import {State, RouteParams} from './types'
+
+export function getCurrentRoute(state: State) {
+  let node = state.routes[state.index || 0]
+  while (node.state?.routes && typeof node.state?.index === 'number') {
+    node = node.state?.routes[node.state?.index]
+  }
+  return node
+}
+
+export function isStateAtTabRoot(state: State | undefined) {
+  if (!state) {
+    // NOTE
+    // if state is not defined it's because init is occuring
+    // and therefore we can safely assume we're at root
+    // -prf
+    return true
+  }
+  const currentRoute = getCurrentRoute(state)
+  return (
+    isTab(currentRoute.name, 'Home') ||
+    isTab(currentRoute.name, 'Search') ||
+    isTab(currentRoute.name, 'Notifications')
+  )
+}
+
+export function isTab(current: string, route: string) {
+  // NOTE
+  // our tab routes can be variously referenced by 3 different names
+  // this helper deals with that weirdness
+  // -prf
+  return (
+    current === route ||
+    current === `${route}Tab` ||
+    current === `${route}Inner`
+  )
+}
+
+export enum TabState {
+  InsideAtRoot,
+  Inside,
+  Outside,
+}
+export function getTabState(state: State | undefined, tab: string): TabState {
+  if (!state) {
+    return TabState.Outside
+  }
+  const currentRoute = getCurrentRoute(state)
+  if (isTab(currentRoute.name, tab)) {
+    return TabState.InsideAtRoot
+  } else if (isTab(state.routes[state.index || 0].name, tab)) {
+    return TabState.Inside
+  }
+  return TabState.Outside
+}
+
+export function buildStateObject(
+  stack: string,
+  route: string,
+  params: RouteParams,
+) {
+  if (stack === 'Flat') {
+    return {
+      routes: [{name: route, params}],
+    }
+  }
+  return {
+    routes: [
+      {
+        name: stack,
+        state: {
+          routes: [{name: route, params}],
+        },
+      },
+    ],
+  }
+}
diff --git a/src/lib/routes/router.ts b/src/lib/routes/router.ts
new file mode 100644
index 000000000..05e0a63de
--- /dev/null
+++ b/src/lib/routes/router.ts
@@ -0,0 +1,55 @@
+import {RouteParams, Route} from './types'
+
+export class Router {
+  routes: [string, Route][] = []
+  constructor(description: Record<string, string>) {
+    for (const [screen, pattern] of Object.entries(description)) {
+      this.routes.push([screen, createRoute(pattern)])
+    }
+  }
+
+  matchName(name: string): Route | undefined {
+    for (const [screenName, route] of this.routes) {
+      if (screenName === name) {
+        return route
+      }
+    }
+  }
+
+  matchPath(path: string): [string, RouteParams] {
+    let name = 'NotFound'
+    let params: RouteParams = {}
+    for (const [screenName, route] of this.routes) {
+      const res = route.match(path)
+      if (res) {
+        name = screenName
+        params = res.params
+        break
+      }
+    }
+    return [name, params]
+  }
+}
+
+function createRoute(pattern: string): Route {
+  let matcherReInternal = pattern.replace(
+    /:([\w]+)/g,
+    (_m, name) => `(?<${name}>[^/]+)`,
+  )
+  const matcherRe = new RegExp(`^${matcherReInternal}([?]|$)`, 'i')
+  return {
+    match(path: string) {
+      const res = matcherRe.exec(path)
+      if (res) {
+        return {params: res.groups || {}}
+      }
+      return undefined
+    },
+    build(params: Record<string, string>) {
+      return pattern.replace(
+        /:([\w]+)/g,
+        (_m, name) => params[name] || 'undefined',
+      )
+    },
+  }
+}
diff --git a/src/lib/routes/types.ts b/src/lib/routes/types.ts
new file mode 100644
index 000000000..e339a46bf
--- /dev/null
+++ b/src/lib/routes/types.ts
@@ -0,0 +1,61 @@
+import {NavigationState, PartialState} from '@react-navigation/native'
+import type {NativeStackNavigationProp} from '@react-navigation/native-stack'
+
+export type {NativeStackScreenProps} from '@react-navigation/native-stack'
+
+export type CommonNavigatorParams = {
+  NotFound: undefined
+  Settings: undefined
+  Profile: {name: string}
+  ProfileFollowers: {name: string}
+  ProfileFollows: {name: string}
+  PostThread: {name: string; rkey: string}
+  PostUpvotedBy: {name: string; rkey: string}
+  PostRepostedBy: {name: string; rkey: string}
+  Debug: undefined
+  Log: undefined
+}
+
+export type HomeTabNavigatorParams = CommonNavigatorParams & {
+  Home: undefined
+}
+
+export type SearchTabNavigatorParams = CommonNavigatorParams & {
+  Search: undefined
+}
+
+export type NotificationsTabNavigatorParams = CommonNavigatorParams & {
+  Notifications: undefined
+}
+
+export type FlatNavigatorParams = CommonNavigatorParams & {
+  Home: undefined
+  Search: undefined
+  Notifications: undefined
+}
+
+export type AllNavigatorParams = CommonNavigatorParams & {
+  HomeTab: undefined
+  Home: undefined
+  SearchTab: undefined
+  Search: undefined
+  NotificationsTab: undefined
+  Notifications: undefined
+}
+
+// NOTE
+// this isn't strictly correct but it should be close enough
+// a TS wizard might be able to get this 100%
+// -prf
+export type NavigationProp = NativeStackNavigationProp<AllNavigatorParams>
+
+export type State =
+  | NavigationState
+  | Omit<PartialState<NavigationState>, 'stale'>
+
+export type RouteParams = Record<string, string>
+export type MatchResult = {params: RouteParams}
+export type Route = {
+  match: (path: string) => MatchResult | undefined
+  build: (params: RouteParams) => string
+}
diff --git a/src/lib/styles.ts b/src/lib/styles.ts
index dbce39178..328229f46 100644
--- a/src/lib/styles.ts
+++ b/src/lib/styles.ts
@@ -1,7 +1,5 @@
 import {StyleProp, StyleSheet, TextStyle} from 'react-native'
 import {Theme, TypographyVariant} from './ThemeContext'
-import {isDesktopWeb} from 'platform/detection'
-import {DESKTOP_HEADER_HEIGHT} from './constants'
 
 // 1 is lightest, 2 is light, 3 is mid, 4 is dark, 5 is darkest
 export const colors = {
@@ -161,9 +159,7 @@ export const s = StyleSheet.create({
   // dimensions
   w100pct: {width: '100%'},
   h100pct: {height: '100%'},
-  hContentRegion: isDesktopWeb
-    ? {height: `calc(100vh - ${DESKTOP_HEADER_HEIGHT}px)`}
-    : {height: '100%'},
+  hContentRegion: {height: '100%'},
 
   // text align
   textLeft: {textAlign: 'left'},
diff --git a/src/routes.ts b/src/routes.ts
new file mode 100644
index 000000000..5987177ef
--- /dev/null
+++ b/src/routes.ts
@@ -0,0 +1,16 @@
+import {Router} from 'lib/routes/router'
+
+export const router = new Router({
+  Home: '/',
+  Search: '/search',
+  Notifications: '/notifications',
+  Settings: '/settings',
+  Profile: '/profile/:name',
+  ProfileFollowers: '/profile/:name/followers',
+  ProfileFollows: '/profile/:name/follows',
+  PostThread: '/profile/:name/post/:rkey',
+  PostUpvotedBy: '/profile/:name/post/:rkey/upvoted-by',
+  PostRepostedBy: '/profile/:name/post/:rkey/reposted-by',
+  Debug: '/sys/debug',
+  Log: '/sys/log',
+})
diff --git a/src/state/models/navigation.ts b/src/state/models/navigation.ts
deleted file mode 100644
index 11af65912..000000000
--- a/src/state/models/navigation.ts
+++ /dev/null
@@ -1,434 +0,0 @@
-import {RootStoreModel} from './root-store'
-import {makeAutoObservable} from 'mobx'
-import {TABS_ENABLED} from 'lib/build-flags'
-import * as analytics from 'lib/analytics'
-import {isNative} from 'platform/detection'
-
-let __id = 0
-function genId() {
-  return String(++__id)
-}
-
-// NOTE
-// this model was originally built for a freeform "tabs" concept like a browser
-// we've since decided to pause that idea and do something more traditional
-// until we're fully sure what that is, the tabs are being repurposed into a fixed topology
-// - Tab 0: The "Default" tab
-// - Tab 1: The "Search" tab
-// - Tab 2: The "Notifications" tab
-// These tabs always retain the first item in their history.
-// -prf
-export enum TabPurpose {
-  Default = 0,
-  Search = 1,
-  Notifs = 2,
-}
-
-export const TabPurposeMainPath: Record<TabPurpose, string> = {
-  [TabPurpose.Default]: '/',
-  [TabPurpose.Search]: '/search',
-  [TabPurpose.Notifs]: '/notifications',
-}
-
-interface HistoryItem {
-  url: string
-  ts: number
-  title?: string
-  id: string
-}
-
-export type HistoryPtr = string // `{tabId}-{historyId}`
-
-export class NavigationTabModel {
-  id = genId()
-  history: HistoryItem[]
-  index = 0
-  isNewTab = false
-
-  constructor(public fixedTabPurpose: TabPurpose) {
-    this.history = [
-      {url: TabPurposeMainPath[fixedTabPurpose], ts: Date.now(), id: genId()},
-    ]
-    makeAutoObservable(this, {
-      serialize: false,
-      hydrate: false,
-    })
-  }
-  // accessors
-  // =
-
-  get current() {
-    return this.history[this.index]
-  }
-
-  get canGoBack() {
-    return this.index > 0
-  }
-
-  get canGoForward() {
-    return this.index < this.history.length - 1
-  }
-
-  getBackList(n: number) {
-    const start = Math.max(this.index - n, 0)
-    const end = this.index
-    return this.history.slice(start, end).map((item, i) => ({
-      url: item.url,
-      title: item.title,
-      index: start + i,
-      id: item.id,
-    }))
-  }
-
-  get backTen() {
-    return this.getBackList(10)
-  }
-
-  getForwardList(n: number) {
-    const start = Math.min(this.index + 1, this.history.length)
-    const end = Math.min(this.index + n + 1, this.history.length)
-    return this.history.slice(start, end).map((item, i) => ({
-      url: item.url,
-      title: item.title,
-      index: start + i,
-      id: item.id,
-    }))
-  }
-
-  get forwardTen() {
-    return this.getForwardList(10)
-  }
-
-  // navigation
-  // =
-
-  navigate(url: string, title?: string) {
-    try {
-      const path = url.split('/')[1]
-      analytics.track('Navigation', {
-        path,
-      })
-    } catch (error) {}
-
-    if (this.current?.url === url) {
-      this.refresh()
-    } else {
-      if (this.index < this.history.length - 1) {
-        this.history.length = this.index + 1
-      }
-      // TEMP ensure the tab has its purpose's main view -prf
-      if (this.history.length < 1) {
-        const fixedUrl = TabPurposeMainPath[this.fixedTabPurpose]
-        this.history.push({url: fixedUrl, ts: Date.now(), id: genId()})
-      }
-      this.history.push({url, title, ts: Date.now(), id: genId()})
-      this.index = this.history.length - 1
-      if (!isNative) {
-        window.history.pushState({hindex: this.index, hurl: url}, '', url)
-      }
-    }
-  }
-
-  refresh() {
-    this.history = [
-      ...this.history.slice(0, this.index),
-      {
-        url: this.current.url,
-        title: this.current.title,
-        ts: Date.now(),
-        id: this.current.id,
-      },
-      ...this.history.slice(this.index + 1),
-    ]
-  }
-
-  goBack() {
-    if (this.canGoBack) {
-      this.index--
-      if (!isNative) {
-        window.history.back()
-      }
-    }
-  }
-
-  // TEMP
-  // a helper to bring the tab back to its base state
-  // -prf
-  fixedTabReset() {
-    this.index = 0
-  }
-
-  goForward() {
-    if (this.canGoForward) {
-      this.index++
-      if (!isNative) {
-        window.history.forward()
-      }
-    }
-  }
-
-  goToIndex(index: number) {
-    if (index >= 0 && index <= this.history.length - 1) {
-      const delta = index - this.index
-      this.index = index
-      if (!isNative) {
-        window.history.go(delta)
-      }
-    }
-  }
-
-  setTitle(id: string, title: string) {
-    this.history = this.history.map(h => {
-      if (h.id === id) {
-        return {...h, title}
-      }
-      return h
-    })
-  }
-
-  setIsNewTab(v: boolean) {
-    this.isNewTab = v
-  }
-
-  // browser only
-  // =
-
-  resetTo(url: string) {
-    this.index = 0
-    this.history.push({url, title: '', ts: Date.now(), id: genId()})
-    this.index = this.history.length - 1
-  }
-
-  // persistence
-  // =
-
-  serialize(): unknown {
-    return {
-      history: this.history,
-      index: this.index,
-    }
-  }
-
-  hydrate(_v: unknown) {
-    // TODO fixme
-    // if (isObj(v)) {
-    //   if (hasProp(v, 'history') && Array.isArray(v.history)) {
-    //     for (const item of v.history) {
-    //       if (
-    //         isObj(item) &&
-    //         hasProp(item, 'url') &&
-    //         typeof item.url === 'string'
-    //       ) {
-    //         let copy: HistoryItem = {
-    //           url: item.url,
-    //           ts:
-    //             hasProp(item, 'ts') && typeof item.ts === 'number'
-    //               ? item.ts
-    //               : Date.now(),
-    //         }
-    //         if (hasProp(item, 'title') && typeof item.title === 'string') {
-    //           copy.title = item.title
-    //         }
-    //         this.history.push(copy)
-    //       }
-    //     }
-    //   }
-    //   if (hasProp(v, 'index') && typeof v.index === 'number') {
-    //     this.index = v.index
-    //   }
-    //   if (this.index >= this.history.length - 1) {
-    //     this.index = this.history.length - 1
-    //   }
-    // }
-  }
-}
-
-export class NavigationModel {
-  tabs: NavigationTabModel[] = isNative
-    ? [
-        new NavigationTabModel(TabPurpose.Default),
-        new NavigationTabModel(TabPurpose.Search),
-        new NavigationTabModel(TabPurpose.Notifs),
-      ]
-    : [new NavigationTabModel(TabPurpose.Default)]
-  tabIndex = 0
-
-  constructor(public rootStore: RootStoreModel) {
-    makeAutoObservable(this, {
-      rootStore: false,
-      serialize: false,
-      hydrate: false,
-    })
-  }
-
-  /**
-   * Used only in the web build to sync with browser history state
-   */
-  bindWebNavigation() {
-    if (!isNative) {
-      window.addEventListener('popstate', e => {
-        const {hindex, hurl} = e.state
-        if (hindex >= 0 && hindex <= this.tab.history.length - 1) {
-          this.tab.index = hindex
-        }
-        if (this.tab.current.url !== hurl) {
-          // desynced because they went back to an old tab session-
-          // do a reset to match that
-          this.tab.resetTo(hurl)
-        }
-
-        // sanity check
-        if (this.tab.current.url !== window.location.pathname) {
-          // state has completely desynced, reload
-          window.location.reload()
-        }
-      })
-    }
-  }
-
-  clear() {
-    this.tabs = isNative
-      ? [
-          new NavigationTabModel(TabPurpose.Default),
-          new NavigationTabModel(TabPurpose.Search),
-          new NavigationTabModel(TabPurpose.Notifs),
-        ]
-      : [new NavigationTabModel(TabPurpose.Default)]
-    this.tabIndex = 0
-  }
-
-  // accessors
-  // =
-
-  get tab() {
-    return this.tabs[this.tabIndex]
-  }
-
-  get tabCount() {
-    return this.tabs.length
-  }
-
-  isCurrentScreen(tabId: string, index: number) {
-    return this.tab.id === tabId && this.tab.index === index
-  }
-
-  // navigation
-  // =
-
-  navigate(url: string, title?: string) {
-    this.rootStore.emitNavigation()
-    this.tab.navigate(url, title)
-  }
-
-  refresh() {
-    this.tab.refresh()
-  }
-
-  setTitle(ptr: HistoryPtr, title: string) {
-    const [tid, hid] = ptr.split('-')
-    this.tabs.find(t => t.id === tid)?.setTitle(hid, title)
-  }
-
-  handleLink(url: string) {
-    let path
-    if (url.startsWith('/')) {
-      path = url
-    } else if (url.startsWith('http')) {
-      try {
-        path = new URL(url).pathname
-      } catch (e) {
-        console.error('Invalid url', url, e)
-        return
-      }
-    } else {
-      console.error('Invalid url', url)
-      return
-    }
-    this.navigate(path)
-  }
-
-  // tab management
-  // =
-
-  // TEMP
-  // fixed tab helper function
-  // -prf
-  switchTo(purpose: TabPurpose, reset: boolean) {
-    this.rootStore.emitNavigation()
-    switch (purpose) {
-      case TabPurpose.Notifs:
-        this.tabIndex = 2
-        break
-      case TabPurpose.Search:
-        this.tabIndex = 1
-        break
-      default:
-        this.tabIndex = 0
-    }
-    if (reset) {
-      this.tab.fixedTabReset()
-    }
-  }
-
-  newTab(url: string, title?: string) {
-    if (!TABS_ENABLED) {
-      return this.navigate(url)
-    }
-    const tab = new NavigationTabModel(TabPurpose.Default)
-    tab.navigate(url, title)
-    tab.isNewTab = true
-    this.tabs.push(tab)
-    this.tabIndex = this.tabs.length - 1
-  }
-
-  setActiveTab(tabIndex: number) {
-    if (!TABS_ENABLED) {
-      return
-    }
-    this.tabIndex = Math.max(Math.min(tabIndex, this.tabs.length - 1), 0)
-  }
-
-  closeTab(tabIndex: number) {
-    if (!TABS_ENABLED) {
-      return
-    }
-    this.tabs = [
-      ...this.tabs.slice(0, tabIndex),
-      ...this.tabs.slice(tabIndex + 1),
-    ]
-    if (this.tabs.length === 0) {
-      this.newTab('/')
-    } else if (this.tabIndex >= this.tabs.length) {
-      this.tabIndex = this.tabs.length - 1
-    }
-  }
-
-  // persistence
-  // =
-
-  serialize(): unknown {
-    return {
-      tabs: this.tabs.map(t => t.serialize()),
-      tabIndex: this.tabIndex,
-    }
-  }
-
-  hydrate(_v: unknown) {
-    // TODO fixme
-    this.clear()
-    /*if (isObj(v)) {
-      if (hasProp(v, 'tabs') && Array.isArray(v.tabs)) {
-        for (const tab of v.tabs) {
-          const copy = new NavigationTabModel()
-          copy.hydrate(tab)
-          if (copy.history.length) {
-            this.tabs.push(copy)
-          }
-        }
-      }
-      if (hasProp(v, 'tabIndex') && typeof v.tabIndex === 'number') {
-        this.tabIndex = v.tabIndex
-      }
-    }*/
-  }
-}
diff --git a/src/state/models/root-store.ts b/src/state/models/root-store.ts
index 4b62f501e..203dacce8 100644
--- a/src/state/models/root-store.ts
+++ b/src/state/models/root-store.ts
@@ -11,12 +11,12 @@ import {z} from 'zod'
 import {isObj, hasProp} from 'lib/type-guards'
 import {LogModel} from './log'
 import {SessionModel} from './session'
-import {NavigationModel} from './navigation'
 import {ShellUiModel} from './shell-ui'
 import {ProfilesViewModel} from './profiles-view'
 import {LinkMetasViewModel} from './link-metas-view'
 import {NotificationsViewItemModel} from './notifications-view'
 import {MeModel} from './me'
+import {resetToTab} from '../../Navigation'
 
 export const appInfo = z.object({
   build: z.string(),
@@ -31,7 +31,6 @@ export class RootStoreModel {
   appInfo?: AppInfo
   log = new LogModel()
   session = new SessionModel(this)
-  nav = new NavigationModel(this)
   shell = new ShellUiModel(this)
   me = new MeModel(this)
   profiles = new ProfilesViewModel(this)
@@ -82,7 +81,6 @@ export class RootStoreModel {
       log: this.log.serialize(),
       session: this.session.serialize(),
       me: this.me.serialize(),
-      nav: this.nav.serialize(),
       shell: this.shell.serialize(),
     }
   }
@@ -101,9 +99,6 @@ export class RootStoreModel {
       if (hasProp(v, 'me')) {
         this.me.hydrate(v.me)
       }
-      if (hasProp(v, 'nav')) {
-        this.nav.hydrate(v.nav)
-      }
       if (hasProp(v, 'session')) {
         this.session.hydrate(v.session)
       }
@@ -144,7 +139,7 @@ export class RootStoreModel {
    */
   async handleSessionDrop() {
     this.log.debug('RootStoreModel:handleSessionDrop')
-    this.nav.clear()
+    resetToTab('HomeTab')
     this.me.clear()
     this.emitSessionDropped()
   }
@@ -155,7 +150,7 @@ export class RootStoreModel {
   clearAllSessionState() {
     this.log.debug('RootStoreModel:clearAllSessionState')
     this.session.clear()
-    this.nav.clear()
+    resetToTab('HomeTab')
     this.me.clear()
   }
 
@@ -203,6 +198,7 @@ export class RootStoreModel {
   }
 
   // the current screen has changed
+  // TODO is this still needed?
   onNavigation(handler: () => void): EmitterSubscription {
     return DeviceEventEmitter.addListener('navigation', handler)
   }
diff --git a/src/state/models/shell-ui.ts b/src/state/models/shell-ui.ts
index 68d9cd3d0..8e4eed6eb 100644
--- a/src/state/models/shell-ui.ts
+++ b/src/state/models/shell-ui.ts
@@ -108,7 +108,6 @@ export interface ComposerOptsQuote {
   }
 }
 export interface ComposerOpts {
-  imagesOpen?: boolean
   replyTo?: ComposerOptsPostRef
   onPost?: () => void
   quote?: ComposerOptsQuote
@@ -117,7 +116,7 @@ export interface ComposerOpts {
 export class ShellUiModel {
   darkMode = false
   minimalShellMode = false
-  isMainMenuOpen = false
+  isDrawerOpen = false
   isModalActive = false
   activeModals: Modal[] = []
   isLightboxActive = false
@@ -156,8 +155,12 @@ export class ShellUiModel {
     this.minimalShellMode = v
   }
 
-  setMainMenuOpen(v: boolean) {
-    this.isMainMenuOpen = v
+  openDrawer() {
+    this.isDrawerOpen = true
+  }
+
+  closeDrawer() {
+    this.isDrawerOpen = false
   }
 
   openModal(modal: Modal) {
diff --git a/src/state/models/user-local-photos.ts b/src/state/models/user-local-photos.ts
deleted file mode 100644
index b14e8a6a4..000000000
--- a/src/state/models/user-local-photos.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import {PhotoIdentifier} from './../../../node_modules/@react-native-camera-roll/camera-roll/src/CameraRoll'
-import {makeAutoObservable, runInAction} from 'mobx'
-import {CameraRoll} from '@react-native-camera-roll/camera-roll'
-import {RootStoreModel} from './root-store'
-
-export type {PhotoIdentifier} from './../../../node_modules/@react-native-camera-roll/camera-roll/src/CameraRoll'
-
-export class UserLocalPhotosModel {
-  // state
-  photos: PhotoIdentifier[] = []
-
-  constructor(public rootStore: RootStoreModel) {
-    makeAutoObservable(this, {
-      rootStore: false,
-    })
-  }
-
-  async setup() {
-    const r = await CameraRoll.getPhotos({first: 20})
-    runInAction(() => {
-      this.photos = r.edges
-    })
-  }
-}
diff --git a/src/view/com/composer/ComposePost.tsx b/src/view/com/composer/Composer.tsx
index f45c6340d..e9b728d73 100644
--- a/src/view/com/composer/ComposePost.tsx
+++ b/src/view/com/composer/Composer.tsx
@@ -1,74 +1,47 @@
-import React, {useEffect, useMemo, useRef, useState} from 'react'
+import React, {useEffect, useRef, useState} from 'react'
 import {observer} from 'mobx-react-lite'
 import {
   ActivityIndicator,
   KeyboardAvoidingView,
-  NativeSyntheticEvent,
   Platform,
   SafeAreaView,
   ScrollView,
   StyleSheet,
-  TextInputSelectionChangeEventData,
   TouchableOpacity,
   TouchableWithoutFeedback,
   View,
 } from 'react-native'
 import LinearGradient from 'react-native-linear-gradient'
-import {
-  FontAwesomeIcon,
-  FontAwesomeIconStyle,
-} from '@fortawesome/react-native-fontawesome'
+import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
 import {useAnalytics} from 'lib/analytics'
-import _isEqual from 'lodash.isequal'
 import {UserAutocompleteViewModel} from 'state/models/user-autocomplete-view'
-import {Autocomplete} from './autocomplete/Autocomplete'
 import {ExternalEmbed} from './ExternalEmbed'
 import {Text} from '../util/text/Text'
 import * as Toast from '../util/Toast'
 import {TextInput, TextInputRef} from './text-input/TextInput'
 import {CharProgress} from './char-progress/CharProgress'
-import {TextLink} from '../util/Link'
 import {UserAvatar} from '../util/UserAvatar'
 import {useStores} from 'state/index'
 import * as apilib from 'lib/api/index'
 import {ComposerOpts} from 'state/models/shell-ui'
 import {s, colors, gradients} from 'lib/styles'
 import {cleanError} from 'lib/strings/errors'
-import {detectLinkables, extractEntities} from 'lib/strings/rich-text-detection'
-import {getLinkMeta} from 'lib/link-meta/link-meta'
-import {getPostAsQuote} from 'lib/link-meta/bsky'
-import {getImageDim, downloadAndResize} from 'lib/media/manip'
-import {PhotoCarouselPicker} from './photos/PhotoCarouselPicker'
-import {cropAndCompressFlow, pickImagesFlow} from '../../../lib/media/picker'
-import {getMentionAt, insertMentionAt} from 'lib/strings/mention-manip'
-import {isBskyPostUrl} from 'lib/strings/url-helpers'
-import {SelectedPhoto} from './SelectedPhoto'
+import {SelectPhotoBtn} from './photos/SelectPhotoBtn'
+import {OpenCameraBtn} from './photos/OpenCameraBtn'
+import {SelectedPhotos} from './photos/SelectedPhotos'
 import {usePalette} from 'lib/hooks/usePalette'
-import {
-  POST_IMG_MAX_WIDTH,
-  POST_IMG_MAX_HEIGHT,
-  POST_IMG_MAX_SIZE,
-} from 'lib/constants'
-import {isWeb} from 'platform/detection'
 import QuoteEmbed from '../util/PostEmbeds/QuoteEmbed'
+import {useExternalLinkFetch} from './useExternalLinkFetch'
 
 const MAX_TEXT_LENGTH = 256
-const HITSLOP = {left: 10, top: 10, right: 10, bottom: 10}
-
-interface Selection {
-  start: number
-  end: number
-}
 
 export const ComposePost = observer(function ComposePost({
   replyTo,
-  imagesOpen,
   onPost,
   onClose,
   quote: initQuote,
 }: {
   replyTo?: ComposerOpts['replyTo']
-  imagesOpen?: ComposerOpts['imagesOpen']
   onPost?: ComposerOpts['onPost']
   onClose: () => void
   quote?: ComposerOpts['quote']
@@ -77,7 +50,6 @@ export const ComposePost = observer(function ComposePost({
   const pal = usePalette('default')
   const store = useStores()
   const textInput = useRef<TextInputRef>(null)
-  const textInputSelection = useRef<Selection>({start: 0, end: 0})
   const [isProcessing, setIsProcessing] = useState(false)
   const [processingState, setProcessingState] = useState('')
   const [error, setError] = useState('')
@@ -85,15 +57,8 @@ export const ComposePost = observer(function ComposePost({
   const [quote, setQuote] = useState<ComposerOpts['quote'] | undefined>(
     initQuote,
   )
-  const [extLink, setExtLink] = useState<apilib.ExternalEmbedDraft | undefined>(
-    undefined,
-  )
-  const [suggestedExtLinks, setSuggestedExtLinks] = useState<Set<string>>(
-    new Set(),
-  )
-  const [isSelectingPhotos, setIsSelectingPhotos] = useState(
-    imagesOpen || false,
-  )
+  const {extLink, setExtLink} = useExternalLinkFetch({setQuote})
+  const [suggestedLinks, setSuggestedLinks] = useState<Set<string>>(new Set())
   const [selectedPhotos, setSelectedPhotos] = useState<string[]>([])
 
   const autocompleteView = React.useMemo<UserAutocompleteViewModel>(
@@ -106,85 +71,16 @@ export const ComposePost = observer(function ComposePost({
   // is focused during unmount, an exception will throw (seems that a blur method isnt implemented)
   // manually blurring before closing gets around that
   // -prf
-  const hackfixOnClose = () => {
+  const hackfixOnClose = React.useCallback(() => {
     textInput.current?.blur()
     onClose()
-  }
+  }, [textInput, onClose])
 
   // initial setup
   useEffect(() => {
     autocompleteView.setup()
   }, [autocompleteView])
 
-  // external link metadata-fetch flow
-  useEffect(() => {
-    let aborted = false
-    const cleanup = () => {
-      aborted = true
-    }
-    if (!extLink) {
-      return cleanup
-    }
-    if (!extLink.meta) {
-      if (isBskyPostUrl(extLink.uri)) {
-        getPostAsQuote(store, extLink.uri).then(
-          newQuote => {
-            if (aborted) {
-              return
-            }
-            setQuote(newQuote)
-            setExtLink(undefined)
-          },
-          err => {
-            store.log.error('Failed to fetch post for quote embedding', {err})
-            setExtLink(undefined)
-          },
-        )
-      } else {
-        getLinkMeta(store, extLink.uri).then(meta => {
-          if (aborted) {
-            return
-          }
-          setExtLink({
-            uri: extLink.uri,
-            isLoading: !!meta.image,
-            meta,
-          })
-        })
-      }
-      return cleanup
-    }
-    if (extLink.isLoading && extLink.meta?.image && !extLink.localThumb) {
-      downloadAndResize({
-        uri: extLink.meta.image,
-        width: 2000,
-        height: 2000,
-        mode: 'contain',
-        maxSize: 1000000,
-        timeout: 15e3,
-      })
-        .catch(() => undefined)
-        .then(localThumb => {
-          if (aborted) {
-            return
-          }
-          setExtLink({
-            ...extLink,
-            isLoading: false, // done
-            localThumb,
-          })
-        })
-      return cleanup
-    }
-    if (extLink.isLoading) {
-      setExtLink({
-        ...extLink,
-        isLoading: false, // done
-      })
-    }
-    return cleanup
-  }, [store, extLink])
-
   useEffect(() => {
     // HACK
     // wait a moment before focusing the input to resolve some layout bugs with the keyboard-avoiding-view
@@ -202,95 +98,36 @@ export const ComposePost = observer(function ComposePost({
     }
   }, [])
 
-  const onPressContainer = () => {
+  const onPressContainer = React.useCallback(() => {
     textInput.current?.focus()
-  }
-  const onPressSelectPhotos = async () => {
-    track('ComposePost:SelectPhotos')
-    if (isWeb) {
-      if (selectedPhotos.length < 4) {
-        const images = await pickImagesFlow(
-          store,
-          4 - selectedPhotos.length,
-          {width: POST_IMG_MAX_WIDTH, height: POST_IMG_MAX_HEIGHT},
-          POST_IMG_MAX_SIZE,
-        )
-        setSelectedPhotos([...selectedPhotos, ...images])
-      }
-    } else {
-      if (isSelectingPhotos) {
-        setIsSelectingPhotos(false)
-      } else if (selectedPhotos.length < 4) {
-        setIsSelectingPhotos(true)
-      }
-    }
-  }
-  const onSelectPhotos = (photos: string[]) => {
-    track('ComposePost:SelectPhotos:Done')
-    setSelectedPhotos(photos)
-    if (photos.length >= 4) {
-      setIsSelectingPhotos(false)
-    }
-  }
-  const onPressAddLinkCard = (uri: string) => {
-    setExtLink({uri, isLoading: true})
-  }
-  const onChangeText = (newText: string) => {
-    setText(newText)
+  }, [textInput])
 
-    const prefix = getMentionAt(newText, textInputSelection.current?.start || 0)
-    if (prefix) {
-      autocompleteView.setActive(true)
-      autocompleteView.setPrefix(prefix.value)
-    } else {
-      autocompleteView.setActive(false)
-    }
+  const onSelectPhotos = React.useCallback(
+    (photos: string[]) => {
+      track('Composer:SelectedPhotos')
+      setSelectedPhotos(photos)
+    },
+    [track, setSelectedPhotos],
+  )
 
-    if (!extLink) {
-      const ents = extractEntities(newText)?.filter(ent => ent.type === 'link')
-      const set = new Set(ents ? ents.map(e => e.value) : [])
-      if (!_isEqual(set, suggestedExtLinks)) {
-        setSuggestedExtLinks(set)
-      }
-    }
-  }
-  const onPaste = async (err: string | undefined, uris: string[]) => {
-    if (err) {
-      return setError(cleanError(err))
-    }
-    if (selectedPhotos.length >= 4) {
-      return
-    }
-    const imgUri = uris.find(uri => /\.(jpe?g|png)$/.test(uri))
-    if (imgUri) {
-      let imgDim
-      try {
-        imgDim = await getImageDim(imgUri)
-      } catch (e) {
-        imgDim = {width: POST_IMG_MAX_WIDTH, height: POST_IMG_MAX_HEIGHT}
+  const onPressAddLinkCard = React.useCallback(
+    (uri: string) => {
+      setExtLink({uri, isLoading: true})
+    },
+    [setExtLink],
+  )
+
+  const onPhotoPasted = React.useCallback(
+    async (uri: string) => {
+      if (selectedPhotos.length >= 4) {
+        return
       }
-      const finalImgPath = await cropAndCompressFlow(
-        store,
-        imgUri,
-        imgDim,
-        {width: POST_IMG_MAX_WIDTH, height: POST_IMG_MAX_HEIGHT},
-        POST_IMG_MAX_SIZE,
-      )
-      onSelectPhotos([...selectedPhotos, finalImgPath])
-    }
-  }
-  const onSelectionChange = (
-    evt: NativeSyntheticEvent<TextInputSelectionChangeEventData>,
-  ) => {
-    // NOTE we track the input selection using a ref to avoid excessive renders -prf
-    textInputSelection.current = evt.nativeEvent.selection
-  }
-  const onSelectAutocompleteItem = (item: string) => {
-    setText(insertMentionAt(text, textInputSelection.current?.start || 0, item))
-    autocompleteView.setActive(false)
-  }
-  const onPressCancel = () => hackfixOnClose()
-  const onPressPublish = async () => {
+      onSelectPhotos([...selectedPhotos, uri])
+    },
+    [selectedPhotos, onSelectPhotos],
+  )
+
+  const onPressPublish = React.useCallback(async () => {
     if (isProcessing) {
       return
     }
@@ -332,7 +169,22 @@ export const ComposePost = observer(function ComposePost({
     onPost?.()
     hackfixOnClose()
     Toast.show(`Your ${replyTo ? 'reply' : 'post'} has been published`)
-  }
+  }, [
+    isProcessing,
+    text,
+    setError,
+    setIsProcessing,
+    replyTo,
+    autocompleteView.knownHandles,
+    extLink,
+    hackfixOnClose,
+    onPost,
+    quote,
+    selectedPhotos,
+    setExtLink,
+    store,
+    track,
+  ])
 
   const canPost = text.length <= MAX_TEXT_LENGTH
 
@@ -346,25 +198,6 @@ export const ComposePost = observer(function ComposePost({
     ? 'Write a comment'
     : "What's up?"
 
-  const textDecorated = useMemo(() => {
-    let i = 0
-    return detectLinkables(text).map(v => {
-      if (typeof v === 'string') {
-        return (
-          <Text key={i++} style={[pal.text, styles.textInputFormatting]}>
-            {v}
-          </Text>
-        )
-      } else {
-        return (
-          <Text key={i++} style={[pal.link, styles.textInputFormatting]}>
-            {v.link}
-          </Text>
-        )
-      }
-    })
-  }, [text, pal.link, pal.text])
-
   return (
     <KeyboardAvoidingView
       testID="composePostView"
@@ -375,7 +208,7 @@ export const ComposePost = observer(function ComposePost({
           <View style={styles.topbar}>
             <TouchableOpacity
               testID="composerCancelButton"
-              onPress={onPressCancel}>
+              onPress={hackfixOnClose}>
               <Text style={[pal.link, s.f18]}>Cancel</Text>
             </TouchableOpacity>
             <View style={s.flex1} />
@@ -423,19 +256,11 @@ export const ComposePost = observer(function ComposePost({
           <ScrollView style={s.flex1}>
             {replyTo ? (
               <View style={[pal.border, styles.replyToLayout]}>
-                <UserAvatar
-                  handle={replyTo.author.handle}
-                  displayName={replyTo.author.displayName}
-                  avatar={replyTo.author.avatar}
-                  size={50}
-                />
+                <UserAvatar avatar={replyTo.author.avatar} size={50} />
                 <View style={styles.replyToPost}>
-                  <TextLink
-                    type="xl-medium"
-                    href={`/profile/${replyTo.author.handle}`}
-                    text={replyTo.author.displayName || replyTo.author.handle}
-                    style={[pal.text]}
-                  />
+                  <Text type="xl-medium" style={[pal.text]}>
+                    {replyTo.author.displayName || replyTo.author.handle}
+                  </Text>
                   <Text type="post-text" style={pal.text} numberOfLines={6}>
                     {replyTo.text}
                   </Text>
@@ -449,26 +274,18 @@ export const ComposePost = observer(function ComposePost({
                 styles.textInputLayout,
                 selectTextInputLayout,
               ]}>
-              <UserAvatar
-                handle={store.me.handle || ''}
-                displayName={store.me.displayName}
-                avatar={store.me.avatar}
-                size={50}
-              />
+              <UserAvatar avatar={store.me.avatar} size={50} />
               <TextInput
-                testID="composerTextInput"
-                innerRef={textInput}
-                onChangeText={(str: string) => onChangeText(str)}
-                onPaste={onPaste}
-                onSelectionChange={onSelectionChange}
+                ref={textInput}
+                text={text}
                 placeholder={selectTextInputPlaceholder}
-                style={[
-                  pal.text,
-                  styles.textInput,
-                  styles.textInputFormatting,
-                ]}>
-                {textDecorated}
-              </TextInput>
+                suggestedLinks={suggestedLinks}
+                autocompleteView={autocompleteView}
+                onTextChanged={setText}
+                onPhotoPasted={onPhotoPasted}
+                onSuggestedLinksChanged={setSuggestedLinks}
+                onError={setError}
+              />
             </View>
 
             {quote ? (
@@ -477,7 +294,7 @@ export const ComposePost = observer(function ComposePost({
               </View>
             ) : undefined}
 
-            <SelectedPhoto
+            <SelectedPhotos
               selectedPhotos={selectedPhotos}
               onSelectPhotos={onSelectPhotos}
             />
@@ -488,17 +305,12 @@ export const ComposePost = observer(function ComposePost({
               />
             )}
           </ScrollView>
-          {isSelectingPhotos && selectedPhotos.length < 4 ? (
-            <PhotoCarouselPicker
-              selectedPhotos={selectedPhotos}
-              onSelectPhotos={onSelectPhotos}
-            />
-          ) : !extLink &&
-            selectedPhotos.length === 0 &&
-            suggestedExtLinks.size > 0 &&
-            !quote ? (
+          {!extLink &&
+          selectedPhotos.length === 0 &&
+          suggestedLinks.size > 0 &&
+          !quote ? (
             <View style={s.mb5}>
-              {Array.from(suggestedExtLinks).map(url => (
+              {Array.from(suggestedLinks).map(url => (
                 <TouchableOpacity
                   key={`suggested-${url}`}
                   style={[pal.borderDark, styles.addExtLinkBtn]}
@@ -511,31 +323,19 @@ export const ComposePost = observer(function ComposePost({
             </View>
           ) : null}
           <View style={[pal.border, styles.bottomBar]}>
-            {quote ? undefined : (
-              <TouchableOpacity
-                testID="composerSelectPhotosButton"
-                onPress={onPressSelectPhotos}
-                style={[s.pl5]}
-                hitSlop={HITSLOP}>
-                <FontAwesomeIcon
-                  icon={['far', 'image']}
-                  style={
-                    (selectedPhotos.length < 4
-                      ? pal.link
-                      : pal.textLight) as FontAwesomeIconStyle
-                  }
-                  size={24}
-                />
-              </TouchableOpacity>
-            )}
+            <SelectPhotoBtn
+              enabled={!quote && selectedPhotos.length < 4}
+              selectedPhotos={selectedPhotos}
+              onSelectPhotos={setSelectedPhotos}
+            />
+            <OpenCameraBtn
+              enabled={!quote && selectedPhotos.length < 4}
+              selectedPhotos={selectedPhotos}
+              onSelectPhotos={setSelectedPhotos}
+            />
             <View style={s.flex1} />
             <CharProgress count={text.length} />
           </View>
-          <Autocomplete
-            active={autocompleteView.isActive}
-            items={autocompleteView.suggestions}
-            onSelect={onSelectAutocompleteItem}
-          />
         </SafeAreaView>
       </TouchableWithoutFeedback>
     </KeyboardAvoidingView>
@@ -597,18 +397,6 @@ const styles = StyleSheet.create({
     borderTopWidth: 1,
     paddingTop: 16,
   },
-  textInput: {
-    flex: 1,
-    padding: 5,
-    marginLeft: 8,
-    alignSelf: 'flex-start',
-  },
-  textInputFormatting: {
-    fontSize: 18,
-    letterSpacing: 0.2,
-    fontWeight: '400',
-    lineHeight: 23.4, // 1.3*16
-  },
   replyToLayout: {
     flexDirection: 'row',
     borderTopWidth: 1,
diff --git a/src/view/com/composer/ExternalEmbed.tsx b/src/view/com/composer/ExternalEmbed.tsx
index 23dcaffd5..658023330 100644
--- a/src/view/com/composer/ExternalEmbed.tsx
+++ b/src/view/com/composer/ExternalEmbed.tsx
@@ -75,6 +75,7 @@ const styles = StyleSheet.create({
     borderWidth: 1,
     borderRadius: 8,
     marginTop: 20,
+    marginBottom: 10,
   },
   inner: {
     padding: 10,
diff --git a/src/view/com/composer/Prompt.tsx b/src/view/com/composer/Prompt.tsx
index 88d5de2bf..301b90093 100644
--- a/src/view/com/composer/Prompt.tsx
+++ b/src/view/com/composer/Prompt.tsx
@@ -4,12 +4,9 @@ import {UserAvatar} from '../util/UserAvatar'
 import {Text} from '../util/text/Text'
 import {usePalette} from 'lib/hooks/usePalette'
 import {useStores} from 'state/index'
+import {isDesktopWeb} from 'platform/detection'
 
-export function ComposePrompt({
-  onPressCompose,
-}: {
-  onPressCompose: (imagesOpen?: boolean) => void
-}) {
+export function ComposePrompt({onPressCompose}: {onPressCompose: () => void}) {
   const store = useStores()
   const pal = usePalette('default')
   return (
@@ -17,13 +14,13 @@ export function ComposePrompt({
       testID="replyPromptBtn"
       style={[pal.view, pal.border, styles.prompt]}
       onPress={() => onPressCompose()}>
-      <UserAvatar
-        handle={store.me.handle}
-        avatar={store.me.avatar}
-        displayName={store.me.displayName}
-        size={38}
-      />
-      <Text type="xl" style={[pal.text, styles.label]}>
+      <UserAvatar avatar={store.me.avatar} size={38} />
+      <Text
+        type="xl"
+        style={[
+          pal.text,
+          isDesktopWeb ? styles.labelDesktopWeb : styles.labelMobile,
+        ]}>
         Write your reply
       </Text>
     </TouchableOpacity>
@@ -39,7 +36,10 @@ const styles = StyleSheet.create({
     alignItems: 'center',
     borderTopWidth: 1,
   },
-  label: {
+  labelMobile: {
     paddingLeft: 12,
   },
+  labelDesktopWeb: {
+    paddingLeft: 20,
+  },
 })
diff --git a/src/view/com/composer/autocomplete/Autocomplete.tsx b/src/view/com/composer/autocomplete/Autocomplete.tsx
deleted file mode 100644
index 82fb239da..000000000
--- a/src/view/com/composer/autocomplete/Autocomplete.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-import React, {useEffect} from 'react'
-import {
-  Animated,
-  TouchableOpacity,
-  StyleSheet,
-  useWindowDimensions,
-} from 'react-native'
-import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
-import {usePalette} from 'lib/hooks/usePalette'
-import {Text} from '../../util/text/Text'
-
-interface AutocompleteItem {
-  handle: string
-  displayName?: string
-}
-
-export function Autocomplete({
-  active,
-  items,
-  onSelect,
-}: {
-  active: boolean
-  items: AutocompleteItem[]
-  onSelect: (item: string) => void
-}) {
-  const pal = usePalette('default')
-  const winDim = useWindowDimensions()
-  const positionInterp = useAnimatedValue(0)
-
-  useEffect(() => {
-    Animated.timing(positionInterp, {
-      toValue: active ? 1 : 0,
-      duration: 200,
-      useNativeDriver: false,
-    }).start()
-  }, [positionInterp, active])
-
-  const topAnimStyle = {
-    top: positionInterp.interpolate({
-      inputRange: [0, 1],
-      outputRange: [winDim.height, winDim.height / 4],
-    }),
-  }
-  return (
-    <Animated.View style={[styles.outer, pal.view, pal.border, topAnimStyle]}>
-      {items.map((item, i) => (
-        <TouchableOpacity
-          testID="autocompleteButton"
-          key={i}
-          style={[pal.border, styles.item]}
-          onPress={() => onSelect(item.handle)}>
-          <Text type="md-medium" style={pal.text}>
-            {item.displayName || item.handle}
-            <Text type="sm" style={pal.textLight}>
-              &nbsp;@{item.handle}
-            </Text>
-          </Text>
-        </TouchableOpacity>
-      ))}
-    </Animated.View>
-  )
-}
-
-const styles = StyleSheet.create({
-  outer: {
-    position: 'absolute',
-    left: 0,
-    right: 0,
-    bottom: 0,
-    borderTopWidth: 1,
-  },
-  item: {
-    borderBottomWidth: 1,
-    paddingVertical: 16,
-    paddingHorizontal: 16,
-  },
-})
diff --git a/src/view/com/composer/autocomplete/Autocomplete.web.tsx b/src/view/com/composer/autocomplete/Autocomplete.web.tsx
deleted file mode 100644
index b6be1c21e..000000000
--- a/src/view/com/composer/autocomplete/Autocomplete.web.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import React from 'react'
-import {TouchableOpacity, StyleSheet, View} from 'react-native'
-import {usePalette} from 'lib/hooks/usePalette'
-import {Text} from '../../util/text/Text'
-
-interface AutocompleteItem {
-  handle: string
-  displayName?: string
-}
-
-export function Autocomplete({
-  active,
-  items,
-  onSelect,
-}: {
-  active: boolean
-  items: AutocompleteItem[]
-  onSelect: (item: string) => void
-}) {
-  const pal = usePalette('default')
-
-  if (!active) {
-    return <View />
-  }
-  return (
-    <View style={[styles.outer, pal.view, pal.border]}>
-      {items.map((item, i) => (
-        <TouchableOpacity
-          testID="autocompleteButton"
-          key={i}
-          style={[pal.border, styles.item]}
-          onPress={() => onSelect(item.handle)}>
-          <Text type="md-medium" style={pal.text}>
-            {item.displayName || item.handle}
-            <Text type="sm" style={pal.textLight}>
-              &nbsp;@{item.handle}
-            </Text>
-          </Text>
-        </TouchableOpacity>
-      ))}
-    </View>
-  )
-}
-
-const styles = StyleSheet.create({
-  outer: {
-    position: 'absolute',
-    left: 0,
-    right: 0,
-    top: '100%',
-    borderWidth: 1,
-    borderRadius: 8,
-  },
-  item: {
-    borderBottomWidth: 1,
-    paddingVertical: 16,
-    paddingHorizontal: 16,
-  },
-})
diff --git a/src/view/com/composer/photos/OpenCameraBtn.tsx b/src/view/com/composer/photos/OpenCameraBtn.tsx
new file mode 100644
index 000000000..cf4a4c7d1
--- /dev/null
+++ b/src/view/com/composer/photos/OpenCameraBtn.tsx
@@ -0,0 +1,84 @@
+import React from 'react'
+import {TouchableOpacity} from 'react-native'
+import {
+  FontAwesomeIcon,
+  FontAwesomeIconStyle,
+} from '@fortawesome/react-native-fontawesome'
+import {usePalette} from 'lib/hooks/usePalette'
+import {useAnalytics} from 'lib/analytics'
+import {useStores} from 'state/index'
+import {s} from 'lib/styles'
+import {isDesktopWeb} from 'platform/detection'
+import {openCamera} from 'lib/media/picker'
+import {compressIfNeeded} from 'lib/media/manip'
+import {useCameraPermission} from 'lib/hooks/usePermissions'
+import {
+  POST_IMG_MAX_WIDTH,
+  POST_IMG_MAX_HEIGHT,
+  POST_IMG_MAX_SIZE,
+} from 'lib/constants'
+
+const HITSLOP = {left: 10, top: 10, right: 10, bottom: 10}
+
+export function OpenCameraBtn({
+  enabled,
+  selectedPhotos,
+  onSelectPhotos,
+}: {
+  enabled: boolean
+  selectedPhotos: string[]
+  onSelectPhotos: (v: string[]) => void
+}) {
+  const pal = usePalette('default')
+  const {track} = useAnalytics()
+  const store = useStores()
+  const {requestCameraAccessIfNeeded} = useCameraPermission()
+
+  const onPressTakePicture = React.useCallback(async () => {
+    track('Composer:CameraOpened')
+    if (!enabled) {
+      return
+    }
+    try {
+      if (!(await requestCameraAccessIfNeeded())) {
+        return
+      }
+      const cameraRes = await openCamera(store, {
+        mediaType: 'photo',
+        width: POST_IMG_MAX_WIDTH,
+        height: POST_IMG_MAX_HEIGHT,
+        freeStyleCropEnabled: true,
+      })
+      const img = await compressIfNeeded(cameraRes, POST_IMG_MAX_SIZE)
+      onSelectPhotos([...selectedPhotos, img.path])
+    } catch (err: any) {
+      // ignore
+      store.log.warn('Error using camera', err)
+    }
+  }, [
+    track,
+    store,
+    onSelectPhotos,
+    selectedPhotos,
+    enabled,
+    requestCameraAccessIfNeeded,
+  ])
+
+  if (isDesktopWeb) {
+    return <></>
+  }
+
+  return (
+    <TouchableOpacity
+      testID="openCameraButton"
+      onPress={onPressTakePicture}
+      style={[s.pl5]}
+      hitSlop={HITSLOP}>
+      <FontAwesomeIcon
+        icon="camera"
+        style={(enabled ? pal.link : pal.textLight) as FontAwesomeIconStyle}
+        size={24}
+      />
+    </TouchableOpacity>
+  )
+}
diff --git a/src/view/com/composer/photos/PhotoCarouselPicker.tsx b/src/view/com/composer/photos/PhotoCarouselPicker.tsx
deleted file mode 100644
index 580e9746e..000000000
--- a/src/view/com/composer/photos/PhotoCarouselPicker.tsx
+++ /dev/null
@@ -1,187 +0,0 @@
-import React, {useCallback} from 'react'
-import {Image, StyleSheet, TouchableOpacity, ScrollView} from 'react-native'
-import {
-  FontAwesomeIcon,
-  FontAwesomeIconStyle,
-} from '@fortawesome/react-native-fontawesome'
-import {useAnalytics} from 'lib/analytics'
-import {
-  openPicker,
-  openCamera,
-  cropAndCompressFlow,
-} from '../../../../lib/media/picker'
-import {
-  UserLocalPhotosModel,
-  PhotoIdentifier,
-} from 'state/models/user-local-photos'
-import {compressIfNeeded} from 'lib/media/manip'
-import {usePalette} from 'lib/hooks/usePalette'
-import {useStores} from 'state/index'
-import {
-  requestPhotoAccessIfNeeded,
-  requestCameraAccessIfNeeded,
-} from 'lib/permissions'
-import {
-  POST_IMG_MAX_WIDTH,
-  POST_IMG_MAX_HEIGHT,
-  POST_IMG_MAX_SIZE,
-} from 'lib/constants'
-
-export const PhotoCarouselPicker = ({
-  selectedPhotos,
-  onSelectPhotos,
-}: {
-  selectedPhotos: string[]
-  onSelectPhotos: (v: string[]) => void
-}) => {
-  const {track} = useAnalytics()
-  const pal = usePalette('default')
-  const store = useStores()
-  const [isSetup, setIsSetup] = React.useState<boolean>(false)
-
-  const localPhotos = React.useMemo<UserLocalPhotosModel>(
-    () => new UserLocalPhotosModel(store),
-    [store],
-  )
-
-  React.useEffect(() => {
-    // initial setup
-    localPhotos.setup().then(() => {
-      setIsSetup(true)
-    })
-  }, [localPhotos])
-
-  const handleOpenCamera = useCallback(async () => {
-    try {
-      if (!(await requestCameraAccessIfNeeded())) {
-        return
-      }
-      const cameraRes = await openCamera(store, {
-        mediaType: 'photo',
-        width: POST_IMG_MAX_WIDTH,
-        height: POST_IMG_MAX_HEIGHT,
-        freeStyleCropEnabled: true,
-      })
-      const img = await compressIfNeeded(cameraRes, POST_IMG_MAX_SIZE)
-      onSelectPhotos([...selectedPhotos, img.path])
-    } catch (err: any) {
-      // ignore
-      store.log.warn('Error using camera', err)
-    }
-  }, [store, selectedPhotos, onSelectPhotos])
-
-  const handleSelectPhoto = useCallback(
-    async (item: PhotoIdentifier) => {
-      track('PhotoCarouselPicker:PhotoSelected')
-      try {
-        const imgPath = await cropAndCompressFlow(
-          store,
-          item.node.image.uri,
-          {
-            width: item.node.image.width,
-            height: item.node.image.height,
-          },
-          {width: POST_IMG_MAX_WIDTH, height: POST_IMG_MAX_HEIGHT},
-          POST_IMG_MAX_SIZE,
-        )
-        onSelectPhotos([...selectedPhotos, imgPath])
-      } catch (err: any) {
-        // ignore
-        store.log.warn('Error selecting photo', err)
-      }
-    },
-    [track, store, onSelectPhotos, selectedPhotos],
-  )
-
-  const handleOpenGallery = useCallback(async () => {
-    track('PhotoCarouselPicker:GalleryOpened')
-    if (!(await requestPhotoAccessIfNeeded())) {
-      return
-    }
-    const items = await openPicker(store, {
-      multiple: true,
-      maxFiles: 4 - selectedPhotos.length,
-      mediaType: 'photo',
-    })
-    const result = []
-    for (const image of items) {
-      result.push(
-        await cropAndCompressFlow(
-          store,
-          image.path,
-          image,
-          {width: POST_IMG_MAX_WIDTH, height: POST_IMG_MAX_HEIGHT},
-          POST_IMG_MAX_SIZE,
-        ),
-      )
-    }
-    onSelectPhotos([...selectedPhotos, ...result])
-  }, [track, store, selectedPhotos, onSelectPhotos])
-
-  return (
-    <ScrollView
-      testID="photoCarouselPickerView"
-      horizontal
-      style={[pal.view, styles.photosContainer]}
-      keyboardShouldPersistTaps="always"
-      showsHorizontalScrollIndicator={false}>
-      <TouchableOpacity
-        testID="openCameraButton"
-        style={[styles.galleryButton, pal.border, styles.photo]}
-        onPress={handleOpenCamera}>
-        <FontAwesomeIcon
-          icon="camera"
-          size={24}
-          style={pal.link as FontAwesomeIconStyle}
-        />
-      </TouchableOpacity>
-      <TouchableOpacity
-        testID="openGalleryButton"
-        style={[styles.galleryButton, pal.border, styles.photo]}
-        onPress={handleOpenGallery}>
-        <FontAwesomeIcon
-          icon="image"
-          style={pal.link as FontAwesomeIconStyle}
-          size={24}
-        />
-      </TouchableOpacity>
-      {isSetup &&
-        localPhotos.photos.map((item: PhotoIdentifier, index: number) => (
-          <TouchableOpacity
-            testID="openSelectPhotoButton"
-            key={`local-image-${index}`}
-            style={[pal.border, styles.photoButton]}
-            onPress={() => handleSelectPhoto(item)}>
-            <Image style={styles.photo} source={{uri: item.node.image.uri}} />
-          </TouchableOpacity>
-        ))}
-    </ScrollView>
-  )
-}
-
-const styles = StyleSheet.create({
-  photosContainer: {
-    width: '100%',
-    maxHeight: 96,
-    padding: 8,
-    overflow: 'hidden',
-  },
-  galleryButton: {
-    borderWidth: 1,
-    alignItems: 'center',
-    justifyContent: 'center',
-  },
-  photoButton: {
-    width: 75,
-    height: 75,
-    marginRight: 8,
-    borderWidth: 1,
-    borderRadius: 16,
-  },
-  photo: {
-    width: 75,
-    height: 75,
-    marginRight: 8,
-    borderRadius: 16,
-  },
-})
diff --git a/src/view/com/composer/photos/PhotoCarouselPicker.web.tsx b/src/view/com/composer/photos/PhotoCarouselPicker.web.tsx
deleted file mode 100644
index ff4350b0c..000000000
--- a/src/view/com/composer/photos/PhotoCarouselPicker.web.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import React from 'react'
-
-// Not used on Web
-
-export const PhotoCarouselPicker = (_opts: {
-  selectedPhotos: string[]
-  onSelectPhotos: (v: string[]) => void
-}) => {
-  return <></>
-}
diff --git a/src/view/com/composer/photos/SelectPhotoBtn.tsx b/src/view/com/composer/photos/SelectPhotoBtn.tsx
new file mode 100644
index 000000000..bdcb0534a
--- /dev/null
+++ b/src/view/com/composer/photos/SelectPhotoBtn.tsx
@@ -0,0 +1,94 @@
+import React from 'react'
+import {TouchableOpacity} from 'react-native'
+import {
+  FontAwesomeIcon,
+  FontAwesomeIconStyle,
+} from '@fortawesome/react-native-fontawesome'
+import {usePalette} from 'lib/hooks/usePalette'
+import {useAnalytics} from 'lib/analytics'
+import {useStores} from 'state/index'
+import {s} from 'lib/styles'
+import {isDesktopWeb} from 'platform/detection'
+import {openPicker, cropAndCompressFlow, pickImagesFlow} from 'lib/media/picker'
+import {usePhotoLibraryPermission} from 'lib/hooks/usePermissions'
+import {
+  POST_IMG_MAX_WIDTH,
+  POST_IMG_MAX_HEIGHT,
+  POST_IMG_MAX_SIZE,
+} from 'lib/constants'
+
+const HITSLOP = {left: 10, top: 10, right: 10, bottom: 10}
+
+export function SelectPhotoBtn({
+  enabled,
+  selectedPhotos,
+  onSelectPhotos,
+}: {
+  enabled: boolean
+  selectedPhotos: string[]
+  onSelectPhotos: (v: string[]) => void
+}) {
+  const pal = usePalette('default')
+  const {track} = useAnalytics()
+  const store = useStores()
+  const {requestPhotoAccessIfNeeded} = usePhotoLibraryPermission()
+
+  const onPressSelectPhotos = React.useCallback(async () => {
+    track('Composer:GalleryOpened')
+    if (!enabled) {
+      return
+    }
+    if (isDesktopWeb) {
+      const images = await pickImagesFlow(
+        store,
+        4 - selectedPhotos.length,
+        {width: POST_IMG_MAX_WIDTH, height: POST_IMG_MAX_HEIGHT},
+        POST_IMG_MAX_SIZE,
+      )
+      onSelectPhotos([...selectedPhotos, ...images])
+    } else {
+      if (!(await requestPhotoAccessIfNeeded())) {
+        return
+      }
+      const items = await openPicker(store, {
+        multiple: true,
+        maxFiles: 4 - selectedPhotos.length,
+        mediaType: 'photo',
+      })
+      const result = []
+      for (const image of items) {
+        result.push(
+          await cropAndCompressFlow(
+            store,
+            image.path,
+            image,
+            {width: POST_IMG_MAX_WIDTH, height: POST_IMG_MAX_HEIGHT},
+            POST_IMG_MAX_SIZE,
+          ),
+        )
+      }
+      onSelectPhotos([...selectedPhotos, ...result])
+    }
+  }, [
+    track,
+    store,
+    onSelectPhotos,
+    selectedPhotos,
+    enabled,
+    requestPhotoAccessIfNeeded,
+  ])
+
+  return (
+    <TouchableOpacity
+      testID="openGalleryBtn"
+      onPress={onPressSelectPhotos}
+      style={[s.pl5, s.pr20]}
+      hitSlop={HITSLOP}>
+      <FontAwesomeIcon
+        icon={['far', 'image']}
+        style={(enabled ? pal.link : pal.textLight) as FontAwesomeIconStyle}
+        size={24}
+      />
+    </TouchableOpacity>
+  )
+}
diff --git a/src/view/com/composer/SelectedPhoto.tsx b/src/view/com/composer/photos/SelectedPhotos.tsx
index 6aeda33cd..c2a00ce53 100644
--- a/src/view/com/composer/SelectedPhoto.tsx
+++ b/src/view/com/composer/photos/SelectedPhotos.tsx
@@ -4,7 +4,7 @@ import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
 import Image from 'view/com/util/images/Image'
 import {colors} from 'lib/styles'
 
-export const SelectedPhoto = ({
+export const SelectedPhotos = ({
   selectedPhotos,
   onSelectPhotos,
 }: {
diff --git a/src/view/com/composer/text-input/TextInput.tsx b/src/view/com/composer/text-input/TextInput.tsx
index be6150e11..2a40fb518 100644
--- a/src/view/com/composer/text-input/TextInput.tsx
+++ b/src/view/com/composer/text-input/TextInput.tsx
@@ -1,64 +1,222 @@
 import React from 'react'
 import {
   NativeSyntheticEvent,
-  StyleProp,
+  StyleSheet,
   TextInputSelectionChangeEventData,
-  TextStyle,
 } from 'react-native'
 import PasteInput, {
   PastedFile,
   PasteInputRef,
 } from '@mattermost/react-native-paste-input'
+import isEqual from 'lodash.isequal'
+import {UserAutocompleteViewModel} from 'state/models/user-autocomplete-view'
+import {Autocomplete} from './mobile/Autocomplete'
+import {Text} from 'view/com/util/text/Text'
+import {useStores} from 'state/index'
+import {cleanError} from 'lib/strings/errors'
+import {detectLinkables, extractEntities} from 'lib/strings/rich-text-detection'
+import {getImageDim} from 'lib/media/manip'
+import {cropAndCompressFlow} from 'lib/media/picker'
+import {getMentionAt, insertMentionAt} from 'lib/strings/mention-manip'
+import {
+  POST_IMG_MAX_WIDTH,
+  POST_IMG_MAX_HEIGHT,
+  POST_IMG_MAX_SIZE,
+} from 'lib/constants'
 import {usePalette} from 'lib/hooks/usePalette'
+import {useTheme} from 'lib/ThemeContext'
 
-export type TextInputRef = PasteInputRef
+export interface TextInputRef {
+  focus: () => void
+  blur: () => void
+}
 
 interface TextInputProps {
-  testID: string
-  innerRef: React.Ref<TextInputRef>
+  text: string
   placeholder: string
-  style: StyleProp<TextStyle>
-  onChangeText: (str: string) => void
-  onSelectionChange?:
-    | ((e: NativeSyntheticEvent<TextInputSelectionChangeEventData>) => void)
-    | undefined
-  onPaste: (err: string | undefined, uris: string[]) => void
+  suggestedLinks: Set<string>
+  autocompleteView: UserAutocompleteViewModel
+  onTextChanged: (v: string) => void
+  onPhotoPasted: (uri: string) => void
+  onSuggestedLinksChanged: (uris: Set<string>) => void
+  onError: (err: string) => void
 }
 
-export function TextInput({
-  testID,
-  innerRef,
-  placeholder,
-  style,
-  onChangeText,
-  onSelectionChange,
-  onPaste,
-  children,
-}: React.PropsWithChildren<TextInputProps>) {
-  const pal = usePalette('default')
-  const onPasteInner = (err: string | undefined, files: PastedFile[]) => {
-    if (err) {
-      onPaste(err, [])
-    } else {
-      onPaste(
-        undefined,
-        files.map(f => f.uri),
-      )
-    }
-  }
-  return (
-    <PasteInput
-      testID={testID}
-      ref={innerRef}
-      multiline
-      scrollEnabled
-      onChangeText={(str: string) => onChangeText(str)}
-      onSelectionChange={onSelectionChange}
-      onPaste={onPasteInner}
-      placeholder={placeholder}
-      placeholderTextColor={pal.colors.textLight}
-      style={style}>
-      {children}
-    </PasteInput>
-  )
+interface Selection {
+  start: number
+  end: number
 }
+
+export const TextInput = React.forwardRef(
+  (
+    {
+      text,
+      placeholder,
+      suggestedLinks,
+      autocompleteView,
+      onTextChanged,
+      onPhotoPasted,
+      onSuggestedLinksChanged,
+      onError,
+    }: TextInputProps,
+    ref,
+  ) => {
+    const pal = usePalette('default')
+    const store = useStores()
+    const textInput = React.useRef<PasteInputRef>(null)
+    const textInputSelection = React.useRef<Selection>({start: 0, end: 0})
+    const theme = useTheme()
+
+    React.useImperativeHandle(ref, () => ({
+      focus: () => textInput.current?.focus(),
+      blur: () => textInput.current?.blur(),
+    }))
+
+    React.useEffect(() => {
+      // HACK
+      // wait a moment before focusing the input to resolve some layout bugs with the keyboard-avoiding-view
+      // -prf
+      let to: NodeJS.Timeout | undefined
+      if (textInput.current) {
+        to = setTimeout(() => {
+          textInput.current?.focus()
+        }, 250)
+      }
+      return () => {
+        if (to) {
+          clearTimeout(to)
+        }
+      }
+    }, [])
+
+    const onChangeText = React.useCallback(
+      (newText: string) => {
+        onTextChanged(newText)
+
+        const prefix = getMentionAt(
+          newText,
+          textInputSelection.current?.start || 0,
+        )
+        if (prefix) {
+          autocompleteView.setActive(true)
+          autocompleteView.setPrefix(prefix.value)
+        } else {
+          autocompleteView.setActive(false)
+        }
+
+        const ents = extractEntities(newText)?.filter(
+          ent => ent.type === 'link',
+        )
+        const set = new Set(ents ? ents.map(e => e.value) : [])
+        if (!isEqual(set, suggestedLinks)) {
+          onSuggestedLinksChanged(set)
+        }
+      },
+      [
+        onTextChanged,
+        autocompleteView,
+        suggestedLinks,
+        onSuggestedLinksChanged,
+      ],
+    )
+
+    const onPaste = React.useCallback(
+      async (err: string | undefined, files: PastedFile[]) => {
+        if (err) {
+          return onError(cleanError(err))
+        }
+        const uris = files.map(f => f.uri)
+        const imgUri = uris.find(uri => /\.(jpe?g|png)$/.test(uri))
+        if (imgUri) {
+          let imgDim
+          try {
+            imgDim = await getImageDim(imgUri)
+          } catch (e) {
+            imgDim = {width: POST_IMG_MAX_WIDTH, height: POST_IMG_MAX_HEIGHT}
+          }
+          const finalImgPath = await cropAndCompressFlow(
+            store,
+            imgUri,
+            imgDim,
+            {width: POST_IMG_MAX_WIDTH, height: POST_IMG_MAX_HEIGHT},
+            POST_IMG_MAX_SIZE,
+          )
+          onPhotoPasted(finalImgPath)
+        }
+      },
+      [store, onError, onPhotoPasted],
+    )
+
+    const onSelectionChange = React.useCallback(
+      (evt: NativeSyntheticEvent<TextInputSelectionChangeEventData>) => {
+        // NOTE we track the input selection using a ref to avoid excessive renders -prf
+        textInputSelection.current = evt.nativeEvent.selection
+      },
+      [textInputSelection],
+    )
+
+    const onSelectAutocompleteItem = React.useCallback(
+      (item: string) => {
+        onChangeText(
+          insertMentionAt(text, textInputSelection.current?.start || 0, item),
+        )
+        autocompleteView.setActive(false)
+      },
+      [onChangeText, text, autocompleteView],
+    )
+
+    const textDecorated = React.useMemo(() => {
+      let i = 0
+      return detectLinkables(text).map(v => {
+        if (typeof v === 'string') {
+          return (
+            <Text key={i++} style={[pal.text, styles.textInputFormatting]}>
+              {v}
+            </Text>
+          )
+        } else {
+          return (
+            <Text key={i++} style={[pal.link, styles.textInputFormatting]}>
+              {v.link}
+            </Text>
+          )
+        }
+      })
+    }, [text, pal.link, pal.text])
+
+    return (
+      <>
+        <PasteInput
+          testID="composerTextInput"
+          ref={textInput}
+          onChangeText={onChangeText}
+          onPaste={onPaste}
+          onSelectionChange={onSelectionChange}
+          placeholder={placeholder}
+          keyboardAppearance={theme.colorScheme}
+          style={[pal.text, styles.textInput, styles.textInputFormatting]}>
+          {textDecorated}
+        </PasteInput>
+        <Autocomplete
+          view={autocompleteView}
+          onSelect={onSelectAutocompleteItem}
+        />
+      </>
+    )
+  },
+)
+
+const styles = StyleSheet.create({
+  textInput: {
+    flex: 1,
+    padding: 5,
+    marginLeft: 8,
+    alignSelf: 'flex-start',
+  },
+  textInputFormatting: {
+    fontSize: 18,
+    letterSpacing: 0.2,
+    fontWeight: '400',
+    lineHeight: 23.4, // 1.3*16
+  },
+})
diff --git a/src/view/com/composer/text-input/TextInput.web.tsx b/src/view/com/composer/text-input/TextInput.web.tsx
index 2b610850c..67ef836a0 100644
--- a/src/view/com/composer/text-input/TextInput.web.tsx
+++ b/src/view/com/composer/text-input/TextInput.web.tsx
@@ -1,58 +1,133 @@
 import React from 'react'
-import {
-  NativeSyntheticEvent,
-  StyleProp,
-  StyleSheet,
-  TextInput as RNTextInput,
-  TextInputSelectionChangeEventData,
-  TextStyle,
-} from 'react-native'
-import {usePalette} from 'lib/hooks/usePalette'
-import {addStyle} from 'lib/styles'
-
-export type TextInputRef = RNTextInput
+import {StyleSheet, View} from 'react-native'
+import {useEditor, EditorContent, JSONContent} from '@tiptap/react'
+import {Document} from '@tiptap/extension-document'
+import {Link} from '@tiptap/extension-link'
+import {Mention} from '@tiptap/extension-mention'
+import {Paragraph} from '@tiptap/extension-paragraph'
+import {Placeholder} from '@tiptap/extension-placeholder'
+import {Text} from '@tiptap/extension-text'
+import isEqual from 'lodash.isequal'
+import {UserAutocompleteViewModel} from 'state/models/user-autocomplete-view'
+import {createSuggestion} from './web/Autocomplete'
+
+export interface TextInputRef {
+  focus: () => void
+  blur: () => void
+}
 
 interface TextInputProps {
-  testID: string
-  innerRef: React.Ref<TextInputRef>
+  text: string
   placeholder: string
-  style: StyleProp<TextStyle>
-  onChangeText: (str: string) => void
-  onSelectionChange?:
-    | ((e: NativeSyntheticEvent<TextInputSelectionChangeEventData>) => void)
-    | undefined
-  onPaste: (err: string | undefined, uris: string[]) => void
+  suggestedLinks: Set<string>
+  autocompleteView: UserAutocompleteViewModel
+  onTextChanged: (v: string) => void
+  onPhotoPasted: (uri: string) => void
+  onSuggestedLinksChanged: (uris: Set<string>) => void
+  onError: (err: string) => void
 }
 
-export function TextInput({
-  testID,
-  innerRef,
-  placeholder,
-  style,
-  onChangeText,
-  onSelectionChange,
-  children,
-}: React.PropsWithChildren<TextInputProps>) {
-  const pal = usePalette('default')
-  style = addStyle(style, styles.input)
-  return (
-    <RNTextInput
-      testID={testID}
-      ref={innerRef}
-      multiline
-      scrollEnabled
-      onChangeText={(str: string) => onChangeText(str)}
-      onSelectionChange={onSelectionChange}
-      placeholder={placeholder}
-      placeholderTextColor={pal.colors.textLight}
-      style={style}>
-      {children}
-    </RNTextInput>
-  )
+export const TextInput = React.forwardRef(
+  (
+    {
+      text,
+      placeholder,
+      suggestedLinks,
+      autocompleteView,
+      onTextChanged,
+      // onPhotoPasted, TODO
+      onSuggestedLinksChanged,
+    }: // onError, TODO
+    TextInputProps,
+    ref,
+  ) => {
+    const editor = useEditor({
+      extensions: [
+        Document,
+        Link.configure({
+          protocols: ['http', 'https'],
+          autolink: true,
+        }),
+        Mention.configure({
+          HTMLAttributes: {
+            class: 'mention',
+          },
+          suggestion: createSuggestion({autocompleteView}),
+        }),
+        Paragraph,
+        Placeholder.configure({
+          placeholder,
+        }),
+        Text,
+      ],
+      content: text,
+      autofocus: true,
+      editable: true,
+      injectCSS: true,
+      onUpdate({editor: editorProp}) {
+        const json = editorProp.getJSON()
+        const newText = editorJsonToText(json).trim()
+        onTextChanged(newText)
+
+        const newSuggestedLinks = new Set(editorJsonToLinks(json))
+        if (!isEqual(newSuggestedLinks, suggestedLinks)) {
+          onSuggestedLinksChanged(newSuggestedLinks)
+        }
+      },
+    })
+
+    React.useImperativeHandle(ref, () => ({
+      focus: () => {}, // TODO
+      blur: () => {}, // TODO
+    }))
+
+    return (
+      <View style={styles.container}>
+        <EditorContent editor={editor} />
+      </View>
+    )
+  },
+)
+
+function editorJsonToText(json: JSONContent): string {
+  let text = ''
+  if (json.type === 'doc' || json.type === 'paragraph') {
+    if (json.content?.length) {
+      for (const node of json.content) {
+        text += editorJsonToText(node)
+      }
+    }
+    text += '\n'
+  } else if (json.type === 'text') {
+    text += json.text || ''
+  } else if (json.type === 'mention') {
+    text += json.attrs?.id || ''
+  }
+  return text
+}
+
+function editorJsonToLinks(json: JSONContent): string[] {
+  let links: string[] = []
+  if (json.content?.length) {
+    for (const node of json.content) {
+      links = links.concat(editorJsonToLinks(node))
+    }
+  }
+
+  const link = json.marks?.find(m => m.type === 'link')
+  if (link?.attrs?.href) {
+    links.push(link.attrs.href)
+  }
+
+  return links
 }
 
 const styles = StyleSheet.create({
-  input: {
-    minHeight: 140,
+  container: {
+    flex: 1,
+    alignSelf: 'flex-start',
+    padding: 5,
+    marginLeft: 8,
+    marginBottom: 10,
   },
 })
diff --git a/src/view/com/composer/text-input/mobile/Autocomplete.tsx b/src/view/com/composer/text-input/mobile/Autocomplete.tsx
new file mode 100644
index 000000000..424a8629f
--- /dev/null
+++ b/src/view/com/composer/text-input/mobile/Autocomplete.tsx
@@ -0,0 +1,75 @@
+import React, {useEffect} from 'react'
+import {
+  Animated,
+  TouchableOpacity,
+  StyleSheet,
+  useWindowDimensions,
+} from 'react-native'
+import {observer} from 'mobx-react-lite'
+import {UserAutocompleteViewModel} from 'state/models/user-autocomplete-view'
+import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
+import {usePalette} from 'lib/hooks/usePalette'
+import {Text} from 'view/com/util/text/Text'
+
+export const Autocomplete = observer(
+  ({
+    view,
+    onSelect,
+  }: {
+    view: UserAutocompleteViewModel
+    onSelect: (item: string) => void
+  }) => {
+    const pal = usePalette('default')
+    const winDim = useWindowDimensions()
+    const positionInterp = useAnimatedValue(0)
+
+    useEffect(() => {
+      Animated.timing(positionInterp, {
+        toValue: view.isActive ? 1 : 0,
+        duration: 200,
+        useNativeDriver: false,
+      }).start()
+    }, [positionInterp, view.isActive])
+
+    const topAnimStyle = {
+      top: positionInterp.interpolate({
+        inputRange: [0, 1],
+        outputRange: [winDim.height, winDim.height / 4],
+      }),
+    }
+    return (
+      <Animated.View style={[styles.outer, pal.view, pal.border, topAnimStyle]}>
+        {view.suggestions.map(item => (
+          <TouchableOpacity
+            testID="autocompleteButton"
+            key={item.handle}
+            style={[pal.border, styles.item]}
+            onPress={() => onSelect(item.handle)}>
+            <Text type="md-medium" style={pal.text}>
+              {item.displayName || item.handle}
+              <Text type="sm" style={pal.textLight}>
+                &nbsp;@{item.handle}
+              </Text>
+            </Text>
+          </TouchableOpacity>
+        ))}
+      </Animated.View>
+    )
+  },
+)
+
+const styles = StyleSheet.create({
+  outer: {
+    position: 'absolute',
+    left: 0,
+    right: 0,
+    bottom: 0,
+    borderTopWidth: 1,
+  },
+  item: {
+    borderBottomWidth: 1,
+    paddingVertical: 16,
+    paddingHorizontal: 16,
+    height: 50,
+  },
+})
diff --git a/src/view/com/composer/text-input/web/Autocomplete.tsx b/src/view/com/composer/text-input/web/Autocomplete.tsx
new file mode 100644
index 000000000..fbe438969
--- /dev/null
+++ b/src/view/com/composer/text-input/web/Autocomplete.tsx
@@ -0,0 +1,157 @@
+import React, {
+  forwardRef,
+  useEffect,
+  useImperativeHandle,
+  useState,
+} from 'react'
+import {ReactRenderer} from '@tiptap/react'
+import tippy, {Instance as TippyInstance} from 'tippy.js'
+import {
+  SuggestionOptions,
+  SuggestionProps,
+  SuggestionKeyDownProps,
+} from '@tiptap/suggestion'
+import {UserAutocompleteViewModel} from 'state/models/user-autocomplete-view'
+
+interface MentionListRef {
+  onKeyDown: (props: SuggestionKeyDownProps) => boolean
+}
+
+export function createSuggestion({
+  autocompleteView,
+}: {
+  autocompleteView: UserAutocompleteViewModel
+}): Omit<SuggestionOptions, 'editor'> {
+  return {
+    async items({query}) {
+      autocompleteView.setActive(true)
+      await autocompleteView.setPrefix(query)
+      return autocompleteView.suggestions.slice(0, 8).map(s => s.handle)
+    },
+
+    render: () => {
+      let component: ReactRenderer<MentionListRef> | undefined
+      let popup: TippyInstance[] | undefined
+
+      return {
+        onStart: props => {
+          component = new ReactRenderer(MentionList, {
+            props,
+            editor: props.editor,
+          })
+
+          if (!props.clientRect) {
+            return
+          }
+
+          // @ts-ignore getReferenceClientRect doesnt like that clientRect can return null -prf
+          popup = tippy('body', {
+            getReferenceClientRect: props.clientRect,
+            appendTo: () => document.body,
+            content: component.element,
+            showOnCreate: true,
+            interactive: true,
+            trigger: 'manual',
+            placement: 'bottom-start',
+          })
+        },
+
+        onUpdate(props) {
+          component?.updateProps(props)
+
+          if (!props.clientRect) {
+            return
+          }
+
+          popup?.[0]?.setProps({
+            // @ts-ignore getReferenceClientRect doesnt like that clientRect can return null -prf
+            getReferenceClientRect: props.clientRect,
+          })
+        },
+
+        onKeyDown(props) {
+          if (props.event.key === 'Escape') {
+            popup?.[0]?.hide()
+
+            return true
+          }
+
+          return component?.ref?.onKeyDown(props) || false
+        },
+
+        onExit() {
+          popup?.[0]?.destroy()
+          component?.destroy()
+        },
+      }
+    },
+  }
+}
+
+const MentionList = forwardRef<MentionListRef, SuggestionProps>(
+  (props: SuggestionProps, ref) => {
+    const [selectedIndex, setSelectedIndex] = useState(0)
+
+    const selectItem = (index: number) => {
+      const item = props.items[index]
+
+      if (item) {
+        props.command({id: item})
+      }
+    }
+
+    const upHandler = () => {
+      setSelectedIndex(
+        (selectedIndex + props.items.length - 1) % props.items.length,
+      )
+    }
+
+    const downHandler = () => {
+      setSelectedIndex((selectedIndex + 1) % props.items.length)
+    }
+
+    const enterHandler = () => {
+      selectItem(selectedIndex)
+    }
+
+    useEffect(() => setSelectedIndex(0), [props.items])
+
+    useImperativeHandle(ref, () => ({
+      onKeyDown: ({event}) => {
+        if (event.key === 'ArrowUp') {
+          upHandler()
+          return true
+        }
+
+        if (event.key === 'ArrowDown') {
+          downHandler()
+          return true
+        }
+
+        if (event.key === 'Enter') {
+          enterHandler()
+          return true
+        }
+
+        return false
+      },
+    }))
+
+    return (
+      <div className="items">
+        {props.items.length ? (
+          props.items.map((item, index) => (
+            <button
+              className={`item ${index === selectedIndex ? 'is-selected' : ''}`}
+              key={index}
+              onClick={() => selectItem(index)}>
+              {item}
+            </button>
+          ))
+        ) : (
+          <div className="item">No result</div>
+        )}
+      </div>
+    )
+  },
+)
diff --git a/src/view/com/composer/useExternalLinkFetch.ts b/src/view/com/composer/useExternalLinkFetch.ts
new file mode 100644
index 000000000..75f833e84
--- /dev/null
+++ b/src/view/com/composer/useExternalLinkFetch.ts
@@ -0,0 +1,90 @@
+import {useState, useEffect} from 'react'
+import {useStores} from 'state/index'
+import * as apilib from 'lib/api/index'
+import {getLinkMeta} from 'lib/link-meta/link-meta'
+import {getPostAsQuote} from 'lib/link-meta/bsky'
+import {downloadAndResize} from 'lib/media/manip'
+import {isBskyPostUrl} from 'lib/strings/url-helpers'
+import {ComposerOpts} from 'state/models/shell-ui'
+
+export function useExternalLinkFetch({
+  setQuote,
+}: {
+  setQuote: (opts: ComposerOpts['quote']) => void
+}) {
+  const store = useStores()
+  const [extLink, setExtLink] = useState<apilib.ExternalEmbedDraft | undefined>(
+    undefined,
+  )
+
+  useEffect(() => {
+    let aborted = false
+    const cleanup = () => {
+      aborted = true
+    }
+    if (!extLink) {
+      return cleanup
+    }
+    if (!extLink.meta) {
+      if (isBskyPostUrl(extLink.uri)) {
+        getPostAsQuote(store, extLink.uri).then(
+          newQuote => {
+            if (aborted) {
+              return
+            }
+            setQuote(newQuote)
+            setExtLink(undefined)
+          },
+          err => {
+            store.log.error('Failed to fetch post for quote embedding', {err})
+            setExtLink(undefined)
+          },
+        )
+      } else {
+        getLinkMeta(store, extLink.uri).then(meta => {
+          if (aborted) {
+            return
+          }
+          setExtLink({
+            uri: extLink.uri,
+            isLoading: !!meta.image,
+            meta,
+          })
+        })
+      }
+      return cleanup
+    }
+    if (extLink.isLoading && extLink.meta?.image && !extLink.localThumb) {
+      console.log('attempting download')
+      downloadAndResize({
+        uri: extLink.meta.image,
+        width: 2000,
+        height: 2000,
+        mode: 'contain',
+        maxSize: 1000000,
+        timeout: 15e3,
+      })
+        .catch(() => undefined)
+        .then(localThumb => {
+          if (aborted) {
+            return
+          }
+          setExtLink({
+            ...extLink,
+            isLoading: false, // done
+            localThumb,
+          })
+        })
+      return cleanup
+    }
+    if (extLink.isLoading) {
+      setExtLink({
+        ...extLink,
+        isLoading: false, // done
+      })
+    }
+    return cleanup
+  }, [store, extLink, setQuote])
+
+  return {extLink, setExtLink}
+}
diff --git a/src/view/com/login/CreateAccount.tsx b/src/view/com/login/CreateAccount.tsx
index 3c09a6cc2..a24dc4e35 100644
--- a/src/view/com/login/CreateAccount.tsx
+++ b/src/view/com/login/CreateAccount.tsx
@@ -27,11 +27,13 @@ import {toNiceDomain} from 'lib/strings/url-helpers'
 import {useStores, DEFAULT_SERVICE} from 'state/index'
 import {ServiceDescription} from 'state/models/session'
 import {usePalette} from 'lib/hooks/usePalette'
+import {useTheme} from 'lib/ThemeContext'
 import {cleanError} from 'lib/strings/errors'
 
 export const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => {
   const {track, screen, identify} = useAnalytics()
   const pal = usePalette('default')
+  const theme = useTheme()
   const store = useStores()
   const [isProcessing, setIsProcessing] = React.useState<boolean>(false)
   const [serviceUrl, setServiceUrl] = React.useState<string>(DEFAULT_SERVICE)
@@ -220,6 +222,7 @@ export const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => {
                     autoCapitalize="none"
                     autoCorrect={false}
                     autoFocus
+                    keyboardAppearance={theme.colorScheme}
                     value={inviteCode}
                     onChangeText={setInviteCode}
                     onBlur={onBlurInviteCode}
diff --git a/src/view/com/login/Signin.tsx b/src/view/com/login/Signin.tsx
index 4f994f831..6faf5ff12 100644
--- a/src/view/com/login/Signin.tsx
+++ b/src/view/com/login/Signin.tsx
@@ -26,6 +26,7 @@ import {ServiceDescription} from 'state/models/session'
 import {AccountData} from 'state/models/session'
 import {isNetworkError} from 'lib/strings/errors'
 import {usePalette} from 'lib/hooks/usePalette'
+import {useTheme} from 'lib/ThemeContext'
 import {cleanError} from 'lib/strings/errors'
 
 enum Forms {
@@ -195,12 +196,7 @@ const ChooseAccountForm = ({
           <View
             style={[pal.borderDark, styles.groupContent, styles.noTopBorder]}>
             <View style={s.p10}>
-              <UserAvatar
-                displayName={account.displayName}
-                handle={account.handle}
-                avatar={account.aviUrl}
-                size={30}
-              />
+              <UserAvatar avatar={account.aviUrl} size={30} />
             </View>
             <Text style={styles.accountText}>
               <Text type="lg-bold" style={pal.text}>
@@ -273,6 +269,7 @@ const LoginForm = ({
 }) => {
   const {track} = useAnalytics()
   const pal = usePalette('default')
+  const theme = useTheme()
   const [isProcessing, setIsProcessing] = useState<boolean>(false)
   const [identifier, setIdentifier] = useState<string>(initialHandle)
   const [password, setPassword] = useState<string>('')
@@ -383,6 +380,7 @@ const LoginForm = ({
             autoCapitalize="none"
             autoFocus
             autoCorrect={false}
+            keyboardAppearance={theme.colorScheme}
             value={identifier}
             onChangeText={str => setIdentifier((str || '').toLowerCase())}
             editable={!isProcessing}
@@ -400,6 +398,7 @@ const LoginForm = ({
             placeholderTextColor={pal.colors.textLight}
             autoCapitalize="none"
             autoCorrect={false}
+            keyboardAppearance={theme.colorScheme}
             secureTextEntry
             value={password}
             onChangeText={setPassword}
@@ -479,6 +478,7 @@ const ForgotPasswordForm = ({
   onEmailSent: () => void
 }) => {
   const pal = usePalette('default')
+  const theme = useTheme()
   const [isProcessing, setIsProcessing] = useState<boolean>(false)
   const [email, setEmail] = useState<string>('')
   const {screen} = useAnalytics()
@@ -567,6 +567,7 @@ const ForgotPasswordForm = ({
               autoCapitalize="none"
               autoFocus
               autoCorrect={false}
+              keyboardAppearance={theme.colorScheme}
               value={email}
               onChangeText={setEmail}
               editable={!isProcessing}
@@ -630,11 +631,12 @@ const SetNewPasswordForm = ({
   onPasswordSet: () => void
 }) => {
   const pal = usePalette('default')
+  const theme = useTheme()
   const {screen} = useAnalytics()
 
-  // useEffect(() => {
-  screen('Signin:SetNewPasswordForm')
-  // }, [screen])
+  useEffect(() => {
+    screen('Signin:SetNewPasswordForm')
+  }, [screen])
 
   const [isProcessing, setIsProcessing] = useState<boolean>(false)
   const [resetCode, setResetCode] = useState<string>('')
@@ -692,6 +694,7 @@ const SetNewPasswordForm = ({
               placeholderTextColor={pal.colors.textLight}
               autoCapitalize="none"
               autoCorrect={false}
+              keyboardAppearance={theme.colorScheme}
               autoFocus
               value={resetCode}
               onChangeText={setResetCode}
@@ -710,6 +713,7 @@ const SetNewPasswordForm = ({
               placeholderTextColor={pal.colors.textLight}
               autoCapitalize="none"
               autoCorrect={false}
+              keyboardAppearance={theme.colorScheme}
               secureTextEntry
               value={password}
               onChangeText={setPassword}
diff --git a/src/view/com/modals/ChangeHandle.tsx b/src/view/com/modals/ChangeHandle.tsx
index 519be7b2e..0795d6d20 100644
--- a/src/view/com/modals/ChangeHandle.tsx
+++ b/src/view/com/modals/ChangeHandle.tsx
@@ -17,6 +17,7 @@ import {ServiceDescription} from 'state/models/session'
 import {s} from 'lib/styles'
 import {makeValidHandle, createFullHandle} from 'lib/strings/handles'
 import {usePalette} from 'lib/hooks/usePalette'
+import {useTheme} from 'lib/ThemeContext'
 import {useAnalytics} from 'lib/analytics'
 import {cleanError} from 'lib/strings/errors'
 
@@ -212,6 +213,7 @@ function ProvidedHandleForm({
   setCanSave: (v: boolean) => void
 }) {
   const pal = usePalette('default')
+  const theme = useTheme()
 
   // events
   // =
@@ -239,6 +241,7 @@ function ProvidedHandleForm({
           placeholder="eg alice"
           placeholderTextColor={pal.colors.textLight}
           autoCapitalize="none"
+          keyboardAppearance={theme.colorScheme}
           value={handle}
           onChangeText={onChangeHandle}
           editable={!isProcessing}
@@ -283,6 +286,7 @@ function CustomHandleForm({
   const pal = usePalette('default')
   const palSecondary = usePalette('secondary')
   const palError = usePalette('error')
+  const theme = useTheme()
   const [isVerifying, setIsVerifying] = React.useState(false)
   const [error, setError] = React.useState<string>('')
 
@@ -348,6 +352,7 @@ function CustomHandleForm({
           placeholder="eg alice.com"
           placeholderTextColor={pal.colors.textLight}
           autoCapitalize="none"
+          keyboardAppearance={theme.colorScheme}
           value={handle}
           onChangeText={onChangeHandle}
           editable={!isProcessing}
diff --git a/src/view/com/modals/DeleteAccount.tsx b/src/view/com/modals/DeleteAccount.tsx
index de29e728d..62fa9f386 100644
--- a/src/view/com/modals/DeleteAccount.tsx
+++ b/src/view/com/modals/DeleteAccount.tsx
@@ -12,13 +12,16 @@ import {Text} from '../util/text/Text'
 import {useStores} from 'state/index'
 import {s, colors, gradients} from 'lib/styles'
 import {usePalette} from 'lib/hooks/usePalette'
+import {useTheme} from 'lib/ThemeContext'
 import {ErrorMessage} from '../util/error/ErrorMessage'
 import {cleanError} from 'lib/strings/errors'
+import {resetToTab} from '../../../Navigation'
 
 export const snapPoints = ['60%']
 
 export function Component({}: {}) {
   const pal = usePalette('default')
+  const theme = useTheme()
   const store = useStores()
   const [isEmailSent, setIsEmailSent] = React.useState<boolean>(false)
   const [confirmCode, setConfirmCode] = React.useState<string>('')
@@ -46,7 +49,7 @@ export function Component({}: {}) {
         token: confirmCode,
       })
       Toast.show('Your account has been deleted')
-      store.nav.tab.fixedTabReset()
+      resetToTab('HomeTab')
       store.session.clear()
       store.shell.closeModal()
     } catch (e: any) {
@@ -117,6 +120,7 @@ export function Component({}: {}) {
               style={[styles.textInput, pal.borderDark, pal.text, styles.mb20]}
               placeholder="Confirmation code"
               placeholderTextColor={pal.textLight.color}
+              keyboardAppearance={theme.colorScheme}
               value={confirmCode}
               onChangeText={setConfirmCode}
             />
@@ -127,6 +131,7 @@ export function Component({}: {}) {
               style={[styles.textInput, pal.borderDark, pal.text]}
               placeholder="Password"
               placeholderTextColor={pal.textLight.color}
+              keyboardAppearance={theme.colorScheme}
               secureTextEntry
               value={password}
               onChangeText={setPassword}
diff --git a/src/view/com/modals/EditProfile.tsx b/src/view/com/modals/EditProfile.tsx
index 121831ada..6eb21d17d 100644
--- a/src/view/com/modals/EditProfile.tsx
+++ b/src/view/com/modals/EditProfile.tsx
@@ -20,6 +20,7 @@ import {compressIfNeeded} from 'lib/media/manip'
 import {UserBanner} from '../util/UserBanner'
 import {UserAvatar} from '../util/UserAvatar'
 import {usePalette} from 'lib/hooks/usePalette'
+import {useTheme} from 'lib/ThemeContext'
 import {useAnalytics} from 'lib/analytics'
 import {cleanError, isNetworkError} from 'lib/strings/errors'
 
@@ -35,6 +36,7 @@ export function Component({
   const store = useStores()
   const [error, setError] = useState<string>('')
   const pal = usePalette('default')
+  const theme = useTheme()
   const {track} = useAnalytics()
 
   const [isProcessing, setProcessing] = useState<boolean>(false)
@@ -133,9 +135,7 @@ export function Component({
             <UserAvatar
               size={80}
               avatar={userAvatar}
-              handle={profileView.handle}
               onSelectNewAvatar={onSelectNewAvatar}
-              displayName={profileView.displayName}
             />
           </View>
         </View>
@@ -160,6 +160,7 @@ export function Component({
             style={[styles.textArea, pal.text]}
             placeholder="e.g. Artist, dog-lover, and memelord."
             placeholderTextColor={colors.gray4}
+            keyboardAppearance={theme.colorScheme}
             multiline
             value={description}
             onChangeText={v => setDescription(enforceLen(v, MAX_DESCRIPTION))}
diff --git a/src/view/com/modals/ServerInput.tsx b/src/view/com/modals/ServerInput.tsx
index 5a9a4cfed..1d352cec9 100644
--- a/src/view/com/modals/ServerInput.tsx
+++ b/src/view/com/modals/ServerInput.tsx
@@ -8,12 +8,14 @@ import {ScrollView, TextInput} from './util'
 import {Text} from '../util/text/Text'
 import {useStores} from 'state/index'
 import {s, colors} from 'lib/styles'
+import {useTheme} from 'lib/ThemeContext'
 import {LOCAL_DEV_SERVICE, STAGING_SERVICE, PROD_SERVICE} from 'state/index'
 import {LOGIN_INCLUDE_DEV_SERVERS} from 'lib/build-flags'
 
 export const snapPoints = ['80%']
 
 export function Component({onSelect}: {onSelect: (url: string) => void}) {
+  const theme = useTheme()
   const store = useStores()
   const [customUrl, setCustomUrl] = useState<string>('')
 
@@ -74,6 +76,7 @@ export function Component({onSelect}: {onSelect: (url: string) => void}) {
               autoCapitalize="none"
               autoComplete="off"
               autoCorrect={false}
+              keyboardAppearance={theme.colorScheme}
               value={customUrl}
               onChangeText={setCustomUrl}
             />
diff --git a/src/view/com/modals/crop-image/CropImage.web.tsx b/src/view/com/modals/crop-image/CropImage.web.tsx
index b21681c7f..306686557 100644
--- a/src/view/com/modals/crop-image/CropImage.web.tsx
+++ b/src/view/com/modals/crop-image/CropImage.web.tsx
@@ -5,6 +5,7 @@ import {Slider} from '@miblanchard/react-native-slider'
 import LinearGradient from 'react-native-linear-gradient'
 import {Text} from 'view/com/util/text/Text'
 import {PickedMedia} from 'lib/media/types'
+import {getDataUriSize} from 'lib/media/util'
 import {s, gradients} from 'lib/styles'
 import {useStores} from 'state/index'
 import {usePalette} from 'lib/hooks/usePalette'
@@ -54,7 +55,7 @@ export function Component({
         mediaType: 'photo',
         path: dataUri,
         mime: 'image/jpeg',
-        size: Math.round((dataUri.length * 3) / 4), // very rough estimate
+        size: getDataUriSize(dataUri),
         width: DIMS[as].width,
         height: DIMS[as].height,
       })
diff --git a/src/view/com/notifications/FeedItem.tsx b/src/view/com/notifications/FeedItem.tsx
index acd00a67d..1c2299b03 100644
--- a/src/view/com/notifications/FeedItem.tsx
+++ b/src/view/com/notifications/FeedItem.tsx
@@ -24,7 +24,7 @@ import {Text} from '../util/text/Text'
 import {UserAvatar} from '../util/UserAvatar'
 import {ImageHorzList} from '../util/images/ImageHorzList'
 import {Post} from '../post/Post'
-import {Link} from '../util/Link'
+import {Link, TextLink} from '../util/Link'
 import {usePalette} from 'lib/hooks/usePalette'
 import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
 
@@ -186,15 +186,12 @@ export const FeedItem = observer(function FeedItem({
                 authors={authors}
               />
               <View style={styles.meta}>
-                <Link
+                <TextLink
                   key={authors[0].href}
-                  style={styles.metaItem}
+                  style={[pal.text, s.bold, styles.metaItem]}
                   href={authors[0].href}
-                  title={`@${authors[0].handle}`}>
-                  <Text style={[pal.text, s.bold]} lineHeight={1.2}>
-                    {authors[0].displayName || authors[0].handle}
-                  </Text>
-                </Link>
+                  text={authors[0].displayName || authors[0].handle}
+                />
                 {authors.length > 1 ? (
                   <>
                     <Text style={[styles.metaItem, pal.text]}>and</Text>
@@ -256,13 +253,9 @@ function CondensedAuthorsList({
         <Link
           style={s.mr5}
           href={authors[0].href}
-          title={`@${authors[0].handle}`}>
-          <UserAvatar
-            size={35}
-            displayName={authors[0].displayName}
-            handle={authors[0].handle}
-            avatar={authors[0].avatar}
-          />
+          title={`@${authors[0].handle}`}
+          asAnchor>
+          <UserAvatar size={35} avatar={authors[0].avatar} />
         </Link>
       </View>
     )
@@ -271,12 +264,7 @@ function CondensedAuthorsList({
     <View style={styles.avis}>
       {authors.slice(0, MAX_AUTHORS).map(author => (
         <View key={author.href} style={s.mr5}>
-          <UserAvatar
-            size={35}
-            displayName={author.displayName}
-            handle={author.handle}
-            avatar={author.avatar}
-          />
+          <UserAvatar size={35} avatar={author.avatar} />
         </View>
       ))}
       {authors.length > MAX_AUTHORS ? (
@@ -326,14 +314,10 @@ function ExpandedAuthorsList({
           key={author.href}
           href={author.href}
           title={author.displayName || author.handle}
-          style={styles.expandedAuthor}>
+          style={styles.expandedAuthor}
+          asAnchor>
           <View style={styles.expandedAuthorAvi}>
-            <UserAvatar
-              size={35}
-              displayName={author.displayName}
-              handle={author.handle}
-              avatar={author.avatar}
-            />
+            <UserAvatar size={35} avatar={author.avatar} />
           </View>
           <View style={s.flex1}>
             <Text
diff --git a/src/view/com/post-thread/PostThread.tsx b/src/view/com/post-thread/PostThread.tsx
index 646d4b276..f84593db8 100644
--- a/src/view/com/post-thread/PostThread.tsx
+++ b/src/view/com/post-thread/PostThread.tsx
@@ -1,28 +1,43 @@
 import React, {useRef} from 'react'
 import {observer} from 'mobx-react-lite'
-import {ActivityIndicator} from 'react-native'
+import {ActivityIndicator, StyleSheet, View} from 'react-native'
 import {CenteredView, FlatList} from '../util/Views'
 import {
   PostThreadViewModel,
   PostThreadViewPostModel,
 } from 'state/models/post-thread-view'
 import {PostThreadItem} from './PostThreadItem'
+import {ComposePrompt} from '../composer/Prompt'
 import {ErrorMessage} from '../util/error/ErrorMessage'
 import {s} from 'lib/styles'
+import {isDesktopWeb} from 'platform/detection'
+import {usePalette} from 'lib/hooks/usePalette'
+
+const REPLY_PROMPT = {_reactKey: '__reply__', _isHighlightedPost: false}
+const BOTTOM_BORDER = {
+  _reactKey: '__bottom_border__',
+  _isHighlightedPost: false,
+}
+type YieldedItem = PostThreadViewPostModel | typeof REPLY_PROMPT
 
 export const PostThread = observer(function PostThread({
   uri,
   view,
+  onPressReply,
 }: {
   uri: string
   view: PostThreadViewModel
+  onPressReply: () => void
 }) {
+  const pal = usePalette('default')
   const ref = useRef<FlatList>(null)
   const [isRefreshing, setIsRefreshing] = React.useState(false)
-  const posts = React.useMemo(
-    () => (view.thread ? Array.from(flattenThread(view.thread)) : []),
-    [view.thread],
-  )
+  const posts = React.useMemo(() => {
+    if (view.thread) {
+      return Array.from(flattenThread(view.thread)).concat([BOTTOM_BORDER])
+    }
+    return []
+  }, [view.thread])
 
   // events
   // =
@@ -58,6 +73,23 @@ export const PostThread = observer(function PostThread({
     },
     [ref],
   )
+  const renderItem = React.useCallback(
+    ({item}: {item: YieldedItem}) => {
+      if (item === REPLY_PROMPT) {
+        return <ComposePrompt onPressCompose={onPressReply} />
+      } else if (item === BOTTOM_BORDER) {
+        // HACK
+        // due to some complexities with how flatlist works, this is the easiest way
+        // I could find to get a border positioned directly under the last item
+        // -prf
+        return <View style={[styles.bottomBorder, pal.border]} />
+      } else if (item instanceof PostThreadViewPostModel) {
+        return <PostThreadItem item={item} onPostReply={onRefresh} />
+      }
+      return <></>
+    },
+    [onRefresh, onPressReply, pal],
+  )
 
   // loading
   // =
@@ -81,9 +113,6 @@ export const PostThread = observer(function PostThread({
 
   // loaded
   // =
-  const renderItem = ({item}: {item: PostThreadViewPostModel}) => (
-    <PostThreadItem item={item} onPostReply={onRefresh} />
-  )
   return (
     <FlatList
       ref={ref}
@@ -104,7 +133,7 @@ export const PostThread = observer(function PostThread({
 function* flattenThread(
   post: PostThreadViewPostModel,
   isAscending = false,
-): Generator<PostThreadViewPostModel, void> {
+): Generator<YieldedItem, void> {
   if (post.parent) {
     if ('notFound' in post.parent && post.parent.notFound) {
       // TODO render not found
@@ -113,6 +142,9 @@ function* flattenThread(
     }
   }
   yield post
+  if (isDesktopWeb && post._isHighlightedPost) {
+    yield REPLY_PROMPT
+  }
   if (post.replies?.length) {
     for (const reply of post.replies) {
       if ('notFound' in reply && reply.notFound) {
@@ -125,3 +157,9 @@ function* flattenThread(
     post._hasMore = true
   }
 }
+
+const styles = StyleSheet.create({
+  bottomBorder: {
+    borderBottomWidth: 1,
+  },
+})
diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx
index 1413148a9..17c7943d9 100644
--- a/src/view/com/post-thread/PostThreadItem.tsx
+++ b/src/view/com/post-thread/PostThreadItem.tsx
@@ -135,13 +135,8 @@ export const PostThreadItem = observer(function PostThreadItem({
           ]}>
           <View style={styles.layout}>
             <View style={styles.layoutAvi}>
-              <Link href={authorHref} title={authorTitle}>
-                <UserAvatar
-                  size={52}
-                  displayName={item.post.author.displayName}
-                  handle={item.post.author.handle}
-                  avatar={item.post.author.avatar}
-                />
+              <Link href={authorHref} title={authorTitle} asAnchor>
+                <UserAvatar size={52} avatar={item.post.author.avatar} />
               </Link>
             </View>
             <View style={styles.layoutContent}>
@@ -299,13 +294,8 @@ export const PostThreadItem = observer(function PostThreadItem({
           )}
           <View style={styles.layout}>
             <View style={styles.layoutAvi}>
-              <Link href={authorHref} title={authorTitle}>
-                <UserAvatar
-                  size={52}
-                  displayName={item.post.author.displayName}
-                  handle={item.post.author.handle}
-                  avatar={item.post.author.avatar}
-                />
+              <Link href={authorHref} title={authorTitle} asAnchor>
+                <UserAvatar size={52} avatar={item.post.author.avatar} />
               </Link>
             </View>
             <View style={styles.layoutContent}>
@@ -313,6 +303,7 @@ export const PostThreadItem = observer(function PostThreadItem({
                 authorHandle={item.post.author.handle}
                 authorDisplayName={item.post.author.displayName}
                 timestamp={item.post.indexedAt}
+                postHref={itemHref}
                 did={item.post.author.did}
                 declarationCid={item.post.author.declaration.cid}
               />
diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx
index 7b4161afc..ac7d1cc55 100644
--- a/src/view/com/post/Post.tsx
+++ b/src/view/com/post/Post.tsx
@@ -150,13 +150,8 @@ export const Post = observer(function Post({
         {showReplyLine && <View style={styles.replyLine} />}
         <View style={styles.layout}>
           <View style={styles.layoutAvi}>
-            <Link href={authorHref} title={authorTitle}>
-              <UserAvatar
-                size={52}
-                displayName={item.post.author.displayName}
-                handle={item.post.author.handle}
-                avatar={item.post.author.avatar}
-              />
+            <Link href={authorHref} title={authorTitle} asAnchor>
+              <UserAvatar size={52} avatar={item.post.author.avatar} />
             </Link>
           </View>
           <View style={styles.layoutContent}>
@@ -164,6 +159,7 @@ export const Post = observer(function Post({
               authorHandle={item.post.author.handle}
               authorDisplayName={item.post.author.displayName}
               timestamp={item.post.indexedAt}
+              postHref={itemHref}
               did={item.post.author.did}
               declarationCid={item.post.author.declaration.cid}
             />
diff --git a/src/view/com/posts/Feed.tsx b/src/view/com/posts/Feed.tsx
index 5751faa68..8f57900b5 100644
--- a/src/view/com/posts/Feed.tsx
+++ b/src/view/com/posts/Feed.tsx
@@ -7,6 +7,7 @@ import {
   StyleSheet,
   ViewStyle,
 } from 'react-native'
+import {useNavigation} from '@react-navigation/native'
 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
 import {FontAwesomeIconStyle} from '@fortawesome/react-native-fontawesome'
 import {CenteredView, FlatList} from '../util/Views'
@@ -18,10 +19,10 @@ import {FeedModel} from 'state/models/feed-view'
 import {FeedItem} from './FeedItem'
 import {OnScrollCb} from 'lib/hooks/useOnMainScroll'
 import {s} from 'lib/styles'
-import {useStores} from 'state/index'
 import {useAnalytics} from 'lib/analytics'
 import {usePalette} from 'lib/hooks/usePalette'
 import {MagnifyingGlassIcon} from 'lib/icons'
+import {NavigationProp} from 'lib/routes/types'
 
 const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
 const ERROR_FEED_ITEM = {_reactKey: '__error__'}
@@ -47,9 +48,9 @@ export const Feed = observer(function Feed({
 }) {
   const pal = usePalette('default')
   const palInverted = usePalette('inverted')
-  const store = useStores()
   const {track} = useAnalytics()
   const [isRefreshing, setIsRefreshing] = React.useState(false)
+  const navigation = useNavigation<NavigationProp>()
 
   const data = React.useMemo(() => {
     let feedItems: any[] = []
@@ -112,7 +113,12 @@ export const Feed = observer(function Feed({
             <Button
               type="inverted"
               style={styles.emptyBtn}
-              onPress={() => store.nav.navigate('/search')}>
+              onPress={
+                () =>
+                  navigation.navigate(
+                    'SearchTab',
+                  ) /* TODO make sure it goes to root of the tab */
+              }>
               <Text type="lg-medium" style={palInverted.text}>
                 Find accounts
               </Text>
@@ -134,7 +140,7 @@ export const Feed = observer(function Feed({
       }
       return <FeedItem item={item} showFollowBtn={showPostFollowBtn} />
     },
-    [feed, onPressTryAgain, showPostFollowBtn, pal, palInverted, store.nav],
+    [feed, onPressTryAgain, showPostFollowBtn, pal, palInverted, navigation],
   )
 
   const FeedFooter = React.useCallback(
diff --git a/src/view/com/posts/FeedItem.tsx b/src/view/com/posts/FeedItem.tsx
index 8b9a6eb2c..ec8feb664 100644
--- a/src/view/com/posts/FeedItem.tsx
+++ b/src/view/com/posts/FeedItem.tsx
@@ -9,7 +9,7 @@ import {
   FontAwesomeIconStyle,
 } from '@fortawesome/react-native-fontawesome'
 import {FeedItemModel} from 'state/models/feed-view'
-import {Link} from '../util/Link'
+import {Link, DesktopWebTextLink} from '../util/Link'
 import {Text} from '../util/text/Text'
 import {UserInfoText} from '../util/UserInfoText'
 import {PostMeta} from '../util/PostMeta'
@@ -169,19 +169,24 @@ export const FeedItem = observer(function ({
               lineHeight={1.2}
               numberOfLines={1}>
               Reposted by{' '}
-              {item.reasonRepost.by.displayName || item.reasonRepost.by.handle}
+              <DesktopWebTextLink
+                type="sm-bold"
+                style={pal.textLight}
+                lineHeight={1.2}
+                numberOfLines={1}
+                text={
+                  item.reasonRepost.by.displayName ||
+                  item.reasonRepost.by.handle
+                }
+                href={`/profile/${item.reasonRepost.by.handle}`}
+              />
             </Text>
           </Link>
         )}
         <View style={styles.layout}>
           <View style={styles.layoutAvi}>
-            <Link href={authorHref} title={item.post.author.handle}>
-              <UserAvatar
-                size={52}
-                displayName={item.post.author.displayName}
-                handle={item.post.author.handle}
-                avatar={item.post.author.avatar}
-              />
+            <Link href={authorHref} title={item.post.author.handle} asAnchor>
+              <UserAvatar size={52} avatar={item.post.author.avatar} />
             </Link>
           </View>
           <View style={styles.layoutContent}>
@@ -189,6 +194,7 @@ export const FeedItem = observer(function ({
               authorHandle={item.post.author.handle}
               authorDisplayName={item.post.author.displayName}
               timestamp={item.post.indexedAt}
+              postHref={itemHref}
               did={item.post.author.did}
               declarationCid={item.post.author.declaration.cid}
               showFollowBtn={showFollowBtn}
diff --git a/src/view/com/profile/ProfileCard.tsx b/src/view/com/profile/ProfileCard.tsx
index 3c487b70f..087536c36 100644
--- a/src/view/com/profile/ProfileCard.tsx
+++ b/src/view/com/profile/ProfileCard.tsx
@@ -37,15 +37,11 @@ export function ProfileCard({
       ]}
       href={`/profile/${handle}`}
       title={handle}
-      noFeedback>
+      noFeedback
+      asAnchor>
       <View style={styles.layout}>
         <View style={styles.layoutAvi}>
-          <UserAvatar
-            size={40}
-            displayName={displayName}
-            handle={handle}
-            avatar={avatar}
-          />
+          <UserAvatar size={40} avatar={avatar} />
         </View>
         <View style={styles.layoutContent}>
           <Text
diff --git a/src/view/com/profile/ProfileHeader.tsx b/src/view/com/profile/ProfileHeader.tsx
index 519d224ea..b061aac41 100644
--- a/src/view/com/profile/ProfileHeader.tsx
+++ b/src/view/com/profile/ProfileHeader.tsx
@@ -7,18 +7,18 @@ import {
   TouchableWithoutFeedback,
   View,
 } from 'react-native'
-import LinearGradient from 'react-native-linear-gradient'
 import {
   FontAwesomeIcon,
   FontAwesomeIconStyle,
 } from '@fortawesome/react-native-fontawesome'
+import {useNavigation} from '@react-navigation/native'
 import {BlurView} from '../util/BlurView'
 import {ProfileViewModel} from 'state/models/profile-view'
 import {useStores} from 'state/index'
 import {ProfileImageLightbox} from 'state/models/shell-ui'
 import {pluralize} from 'lib/strings/helpers'
 import {toShareUrl} from 'lib/strings/url-helpers'
-import {s, gradients} from 'lib/styles'
+import {s, colors} from 'lib/styles'
 import {DropdownButton, DropdownItem} from '../util/forms/DropdownButton'
 import * as Toast from '../util/Toast'
 import {LoadingPlaceholder} from '../util/LoadingPlaceholder'
@@ -28,6 +28,8 @@ import {UserAvatar} from '../util/UserAvatar'
 import {UserBanner} from '../util/UserBanner'
 import {usePalette} from 'lib/hooks/usePalette'
 import {useAnalytics} from 'lib/analytics'
+import {NavigationProp} from 'lib/routes/types'
+import {isDesktopWeb} from 'platform/detection'
 
 const BACK_HITSLOP = {left: 30, top: 30, right: 30, bottom: 30}
 
@@ -40,16 +42,17 @@ export const ProfileHeader = observer(function ProfileHeader({
 }) {
   const pal = usePalette('default')
   const store = useStores()
+  const navigation = useNavigation<NavigationProp>()
   const {track} = useAnalytics()
-  const onPressBack = () => {
-    store.nav.tab.goBack()
-  }
-  const onPressAvi = () => {
+  const onPressBack = React.useCallback(() => {
+    navigation.goBack()
+  }, [navigation])
+  const onPressAvi = React.useCallback(() => {
     if (view.avatar) {
       store.shell.openLightbox(new ProfileImageLightbox(view))
     }
-  }
-  const onPressToggleFollow = () => {
+  }, [store, view])
+  const onPressToggleFollow = React.useCallback(() => {
     view?.toggleFollowing().then(
       () => {
         Toast.show(
@@ -60,28 +63,28 @@ export const ProfileHeader = observer(function ProfileHeader({
       },
       err => store.log.error('Failed to toggle follow', err),
     )
-  }
-  const onPressEditProfile = () => {
+  }, [view, store])
+  const onPressEditProfile = React.useCallback(() => {
     track('ProfileHeader:EditProfileButtonClicked')
     store.shell.openModal({
       name: 'edit-profile',
       profileView: view,
       onUpdate: onRefreshAll,
     })
-  }
-  const onPressFollowers = () => {
+  }, [track, store, view, onRefreshAll])
+  const onPressFollowers = React.useCallback(() => {
     track('ProfileHeader:FollowersButtonClicked')
-    store.nav.navigate(`/profile/${view.handle}/followers`)
-  }
-  const onPressFollows = () => {
+    navigation.push('ProfileFollowers', {name: view.handle})
+  }, [track, navigation, view])
+  const onPressFollows = React.useCallback(() => {
     track('ProfileHeader:FollowsButtonClicked')
-    store.nav.navigate(`/profile/${view.handle}/follows`)
-  }
-  const onPressShare = () => {
+    navigation.push('ProfileFollows', {name: view.handle})
+  }, [track, navigation, view])
+  const onPressShare = React.useCallback(() => {
     track('ProfileHeader:ShareButtonClicked')
     Share.share({url: toShareUrl(`/profile/${view.handle}`)})
-  }
-  const onPressMuteAccount = async () => {
+  }, [track, view])
+  const onPressMuteAccount = React.useCallback(async () => {
     track('ProfileHeader:MuteAccountButtonClicked')
     try {
       await view.muteAccount()
@@ -90,8 +93,8 @@ export const ProfileHeader = observer(function ProfileHeader({
       store.log.error('Failed to mute account', e)
       Toast.show(`There was an issue! ${e.toString()}`)
     }
-  }
-  const onPressUnmuteAccount = async () => {
+  }, [track, view, store])
+  const onPressUnmuteAccount = React.useCallback(async () => {
     track('ProfileHeader:UnmuteAccountButtonClicked')
     try {
       await view.unmuteAccount()
@@ -100,14 +103,14 @@ export const ProfileHeader = observer(function ProfileHeader({
       store.log.error('Failed to unmute account', e)
       Toast.show(`There was an issue! ${e.toString()}`)
     }
-  }
-  const onPressReportAccount = () => {
+  }, [track, view, store])
+  const onPressReportAccount = React.useCallback(() => {
     track('ProfileHeader:ReportAccountButtonClicked')
     store.shell.openModal({
       name: 'report-account',
       did: view.did,
     })
-  }
+  }, [track, store, view])
 
   // loading
   // =
@@ -189,23 +192,15 @@ export const ProfileHeader = observer(function ProfileHeader({
               ) : (
                 <TouchableOpacity
                   testID="profileHeaderToggleFollowButton"
-                  onPress={onPressToggleFollow}>
-                  <LinearGradient
-                    colors={[
-                      gradients.blueLight.start,
-                      gradients.blueLight.end,
-                    ]}
-                    start={{x: 0, y: 0}}
-                    end={{x: 1, y: 1}}
-                    style={[styles.btn, styles.gradientBtn]}>
-                    <FontAwesomeIcon
-                      icon="plus"
-                      style={[s.white as FontAwesomeIconStyle, s.mr5]}
-                    />
-                    <Text type="button" style={[s.white, s.bold]}>
-                      Follow
-                    </Text>
-                  </LinearGradient>
+                  onPress={onPressToggleFollow}
+                  style={[styles.btn, styles.primaryBtn]}>
+                  <FontAwesomeIcon
+                    icon="plus"
+                    style={[s.white as FontAwesomeIconStyle, s.mr5]}
+                  />
+                  <Text type="button" style={[s.white, s.bold]}>
+                    Follow
+                  </Text>
                 </TouchableOpacity>
               )}
             </>
@@ -287,24 +282,21 @@ export const ProfileHeader = observer(function ProfileHeader({
           </View>
         ) : undefined}
       </View>
-      <TouchableWithoutFeedback onPress={onPressBack} hitSlop={BACK_HITSLOP}>
-        <View style={styles.backBtnWrapper}>
-          <BlurView style={styles.backBtn} blurType="dark">
-            <FontAwesomeIcon size={18} icon="angle-left" style={s.white} />
-          </BlurView>
-        </View>
-      </TouchableWithoutFeedback>
+      {!isDesktopWeb && (
+        <TouchableWithoutFeedback onPress={onPressBack} hitSlop={BACK_HITSLOP}>
+          <View style={styles.backBtnWrapper}>
+            <BlurView style={styles.backBtn} blurType="dark">
+              <FontAwesomeIcon size={18} icon="angle-left" style={s.white} />
+            </BlurView>
+          </View>
+        </TouchableWithoutFeedback>
+      )}
       <TouchableWithoutFeedback
         testID="profileHeaderAviButton"
         onPress={onPressAvi}>
         <View
           style={[pal.view, {borderColor: pal.colors.background}, styles.avi]}>
-          <UserAvatar
-            size={80}
-            handle={view.handle}
-            displayName={view.displayName}
-            avatar={view.avatar}
-          />
+          <UserAvatar size={80} avatar={view.avatar} />
         </View>
       </TouchableWithoutFeedback>
     </View>
@@ -350,7 +342,8 @@ const styles = StyleSheet.create({
     marginLeft: 'auto',
     marginBottom: 12,
   },
-  gradientBtn: {
+  primaryBtn: {
+    backgroundColor: colors.blue3,
     paddingHorizontal: 24,
     paddingVertical: 6,
   },
diff --git a/src/view/com/util/ErrorBoundary.tsx b/src/view/com/util/ErrorBoundary.tsx
index 017265f48..c7374e195 100644
--- a/src/view/com/util/ErrorBoundary.tsx
+++ b/src/view/com/util/ErrorBoundary.tsx
@@ -1,5 +1,6 @@
 import React, {Component, ErrorInfo, ReactNode} from 'react'
 import {ErrorScreen} from './error/ErrorScreen'
+import {CenteredView} from './Views'
 
 interface Props {
   children?: ReactNode
@@ -27,11 +28,13 @@ export class ErrorBoundary extends Component<Props, State> {
   public render() {
     if (this.state.hasError) {
       return (
-        <ErrorScreen
-          title="Oh no!"
-          message="There was an unexpected issue in the application. Please let us know if this happened to you!"
-          details={this.state.error.toString()}
-        />
+        <CenteredView>
+          <ErrorScreen
+            title="Oh no!"
+            message="There was an unexpected issue in the application. Please let us know if this happened to you!"
+            details={this.state.error.toString()}
+          />
+        </CenteredView>
       )
     }
 
diff --git a/src/view/com/util/Link.tsx b/src/view/com/util/Link.tsx
index bdc447937..cee4d4136 100644
--- a/src/view/com/util/Link.tsx
+++ b/src/view/com/util/Link.tsx
@@ -2,6 +2,8 @@ import React from 'react'
 import {observer} from 'mobx-react-lite'
 import {
   Linking,
+  GestureResponderEvent,
+  Platform,
   StyleProp,
   TouchableWithoutFeedback,
   TouchableOpacity,
@@ -9,10 +11,22 @@ import {
   View,
   ViewStyle,
 } from 'react-native'
+import {
+  useLinkProps,
+  useNavigation,
+  StackActions,
+} from '@react-navigation/native'
 import {Text} from './text/Text'
 import {TypographyVariant} from 'lib/ThemeContext'
+import {NavigationProp} from 'lib/routes/types'
+import {router} from '../../../routes'
 import {useStores, RootStoreModel} from 'state/index'
 import {convertBskyAppUrlIfNeeded} from 'lib/strings/url-helpers'
+import {isDesktopWeb} from 'platform/detection'
+
+type Event =
+  | React.MouseEvent<HTMLAnchorElement, MouseEvent>
+  | GestureResponderEvent
 
 export const Link = observer(function Link({
   style,
@@ -20,30 +34,33 @@ export const Link = observer(function Link({
   title,
   children,
   noFeedback,
+  asAnchor,
 }: {
   style?: StyleProp<ViewStyle>
   href?: string
   title?: string
   children?: React.ReactNode
   noFeedback?: boolean
+  asAnchor?: boolean
 }) {
   const store = useStores()
-  const onPress = () => {
-    if (href) {
-      handleLink(store, href, false)
-    }
-  }
-  const onLongPress = () => {
-    if (href) {
-      handleLink(store, href, true)
-    }
-  }
+  const navigation = useNavigation<NavigationProp>()
+
+  const onPress = React.useCallback(
+    (e?: Event) => {
+      if (typeof href === 'string') {
+        return onPressInner(store, navigation, href, e)
+      }
+    },
+    [store, navigation, href],
+  )
+
   if (noFeedback) {
     return (
       <TouchableWithoutFeedback
         onPress={onPress}
-        onLongPress={onLongPress}
-        delayPressIn={50}>
+        // @ts-ignore web only -prf
+        href={asAnchor ? href : undefined}>
         <View style={style}>
           {children ? children : <Text>{title || 'link'}</Text>}
         </View>
@@ -52,10 +69,10 @@ export const Link = observer(function Link({
   }
   return (
     <TouchableOpacity
+      style={style}
       onPress={onPress}
-      onLongPress={onLongPress}
-      delayPressIn={50}
-      style={style}>
+      // @ts-ignore web only -prf
+      href={asAnchor ? href : undefined}>
       {children ? children : <Text>{title || 'link'}</Text>}
     </TouchableOpacity>
   )
@@ -66,35 +83,123 @@ export const TextLink = observer(function TextLink({
   style,
   href,
   text,
+  numberOfLines,
+  lineHeight,
 }: {
   type?: TypographyVariant
   style?: StyleProp<TextStyle>
   href: string
-  text: string
+  text: string | JSX.Element
+  numberOfLines?: number
+  lineHeight?: number
 }) {
+  const {...props} = useLinkProps({to: href})
   const store = useStores()
-  const onPress = () => {
-    handleLink(store, href, false)
-  }
-  const onLongPress = () => {
-    handleLink(store, href, true)
+  const navigation = useNavigation<NavigationProp>()
+
+  props.onPress = React.useCallback(
+    (e?: Event) => {
+      return onPressInner(store, navigation, href, e)
+    },
+    [store, navigation, href],
+  )
+
+  return (
+    <Text
+      type={type}
+      style={style}
+      numberOfLines={numberOfLines}
+      lineHeight={lineHeight}
+      {...props}>
+      {text}
+    </Text>
+  )
+})
+
+/**
+ * Only acts as a link on desktop web
+ */
+export const DesktopWebTextLink = observer(function DesktopWebTextLink({
+  type = 'md',
+  style,
+  href,
+  text,
+  numberOfLines,
+  lineHeight,
+}: {
+  type?: TypographyVariant
+  style?: StyleProp<TextStyle>
+  href: string
+  text: string | JSX.Element
+  numberOfLines?: number
+  lineHeight?: number
+}) {
+  if (isDesktopWeb) {
+    return (
+      <TextLink
+        type={type}
+        style={style}
+        href={href}
+        text={text}
+        numberOfLines={numberOfLines}
+        lineHeight={lineHeight}
+      />
+    )
   }
   return (
-    <Text type={type} style={style} onPress={onPress} onLongPress={onLongPress}>
+    <Text
+      type={type}
+      style={style}
+      numberOfLines={numberOfLines}
+      lineHeight={lineHeight}>
       {text}
     </Text>
   )
 })
 
-function handleLink(store: RootStoreModel, href: string, longPress: boolean) {
-  href = convertBskyAppUrlIfNeeded(href)
-  if (href.startsWith('http')) {
-    Linking.openURL(href)
-  } else if (longPress) {
-    store.shell.closeModal() // close any active modals
-    store.nav.newTab(href)
-  } else {
-    store.shell.closeModal() // close any active modals
-    store.nav.navigate(href)
+// NOTE
+// we can't use the onPress given by useLinkProps because it will
+// match most paths to the HomeTab routes while we actually want to
+// preserve the tab the app is currently in
+//
+// we also have some additional behaviors - closing the current modal,
+// converting bsky urls, and opening http/s links in the system browser
+//
+// this method copies from the onPress implementation but adds our
+// needed customizations
+// -prf
+function onPressInner(
+  store: RootStoreModel,
+  navigation: NavigationProp,
+  href: string,
+  e?: Event,
+) {
+  let shouldHandle = false
+
+  if (Platform.OS !== 'web' || !e) {
+    shouldHandle = e ? !e.defaultPrevented : true
+  } else if (
+    !e.defaultPrevented && // onPress prevented default
+    // @ts-ignore Web only -prf
+    !(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) && // ignore clicks with modifier keys
+    // @ts-ignore Web only -prf
+    (e.button == null || e.button === 0) && // ignore everything but left clicks
+    // @ts-ignore Web only -prf
+    [undefined, null, '', 'self'].includes(e.currentTarget?.target) // let browser handle "target=_blank" etc.
+  ) {
+    e.preventDefault()
+    shouldHandle = true
+  }
+
+  if (shouldHandle) {
+    href = convertBskyAppUrlIfNeeded(href)
+    if (href.startsWith('http')) {
+      Linking.openURL(href)
+    } else {
+      store.shell.closeModal() // close any active modals
+
+      // @ts-ignore we're not able to type check on this one -prf
+      navigation.dispatch(StackActions.push(...router.matchPath(href)))
+    }
   }
 }
diff --git a/src/view/com/util/LoadLatestBtn.web.tsx b/src/view/com/util/LoadLatestBtn.web.tsx
index 182c1ba5d..ba33f92a7 100644
--- a/src/view/com/util/LoadLatestBtn.web.tsx
+++ b/src/view/com/util/LoadLatestBtn.web.tsx
@@ -2,6 +2,7 @@ import React from 'react'
 import {StyleSheet, TouchableOpacity} from 'react-native'
 import {Text} from './text/Text'
 import {usePalette} from 'lib/hooks/usePalette'
+import {UpIcon} from 'lib/icons'
 
 const HITSLOP = {left: 20, top: 20, right: 20, bottom: 20}
 
@@ -9,10 +10,11 @@ export const LoadLatestBtn = ({onPress}: {onPress: () => void}) => {
   const pal = usePalette('default')
   return (
     <TouchableOpacity
-      style={[pal.view, styles.loadLatest]}
+      style={[pal.view, pal.borderDark, styles.loadLatest]}
       onPress={onPress}
       hitSlop={HITSLOP}>
       <Text type="md-bold" style={pal.text}>
+        <UpIcon size={16} strokeWidth={1} style={[pal.text, styles.icon]} />
         Load new posts
       </Text>
     </TouchableOpacity>
@@ -29,8 +31,15 @@ const styles = StyleSheet.create({
     shadowOpacity: 0.2,
     shadowOffset: {width: 0, height: 2},
     shadowRadius: 4,
-    paddingHorizontal: 24,
+    paddingLeft: 20,
+    paddingRight: 24,
     paddingVertical: 10,
     borderRadius: 30,
+    borderWidth: 1,
+  },
+  icon: {
+    position: 'relative',
+    top: 2,
+    marginRight: 5,
   },
 })
diff --git a/src/view/com/util/PostEmbeds/QuoteEmbed.tsx b/src/view/com/util/PostEmbeds/QuoteEmbed.tsx
index 76b71a53d..f98a66b76 100644
--- a/src/view/com/util/PostEmbeds/QuoteEmbed.tsx
+++ b/src/view/com/util/PostEmbeds/QuoteEmbed.tsx
@@ -25,6 +25,7 @@ const QuoteEmbed = ({quote}: {quote: ComposerOptsQuote}) => {
         authorAvatar={quote.author.avatar}
         authorHandle={quote.author.handle}
         authorDisplayName={quote.author.displayName}
+        postHref={itemHref}
         timestamp={quote.indexedAt}
       />
       <Text type="post-text" style={pal.text} numberOfLines={6}>
diff --git a/src/view/com/util/PostMeta.tsx b/src/view/com/util/PostMeta.tsx
index cde5a3e92..0bb402100 100644
--- a/src/view/com/util/PostMeta.tsx
+++ b/src/view/com/util/PostMeta.tsx
@@ -1,6 +1,7 @@
 import React from 'react'
 import {StyleSheet, View} from 'react-native'
 import {Text} from './text/Text'
+import {DesktopWebTextLink} from './Link'
 import {ago} from 'lib/strings/time'
 import {usePalette} from 'lib/hooks/usePalette'
 import {useStores} from 'state/index'
@@ -12,6 +13,7 @@ interface PostMetaOpts {
   authorAvatar?: string
   authorHandle: string
   authorDisplayName: string | undefined
+  postHref: string
   timestamp: string
   did?: string
   declarationCid?: string
@@ -20,8 +22,8 @@ interface PostMetaOpts {
 
 export const PostMeta = observer(function (opts: PostMetaOpts) {
   const pal = usePalette('default')
-  let displayName = opts.authorDisplayName || opts.authorHandle
-  let handle = opts.authorHandle
+  const displayName = opts.authorDisplayName || opts.authorHandle
+  const handle = opts.authorHandle
   const store = useStores()
   const isMe = opts.did === store.me.did
   const isFollowing =
@@ -41,31 +43,35 @@ export const PostMeta = observer(function (opts: PostMetaOpts) {
   ) {
     // two-liner with follow button
     return (
-      <View style={[styles.metaTwoLine]}>
+      <View style={styles.metaTwoLine}>
         <View>
-          <Text
-            type="lg-bold"
-            style={[pal.text]}
-            numberOfLines={1}
-            lineHeight={1.2}>
-            {displayName}{' '}
-            <Text
+          <View style={styles.metaTwoLineTop}>
+            <DesktopWebTextLink
+              type="lg-bold"
+              style={pal.text}
+              numberOfLines={1}
+              lineHeight={1.2}
+              text={displayName}
+              href={`/profile/${opts.authorHandle}`}
+            />
+            <Text type="md" style={pal.textLight} lineHeight={1.2}>
+              &nbsp;&middot;&nbsp;
+            </Text>
+            <DesktopWebTextLink
               type="md"
               style={[styles.metaItem, pal.textLight]}
-              lineHeight={1.2}>
-              &middot; {ago(opts.timestamp)}
-            </Text>
-          </Text>
-          <Text
+              lineHeight={1.2}
+              text={ago(opts.timestamp)}
+              href={opts.postHref}
+            />
+          </View>
+          <DesktopWebTextLink
             type="md"
             style={[styles.metaItem, pal.textLight]}
-            lineHeight={1.2}>
-            {handle ? (
-              <Text type="md" style={[pal.textLight]}>
-                @{handle}
-              </Text>
-            ) : undefined}
-          </Text>
+            lineHeight={1.2}
+            text={`@${handle}`}
+            href={`/profile/${opts.authorHandle}`}
+          />
         </View>
 
         <View>
@@ -84,31 +90,36 @@ export const PostMeta = observer(function (opts: PostMetaOpts) {
     <View style={styles.meta}>
       {typeof opts.authorAvatar !== 'undefined' && (
         <View style={[styles.metaItem, styles.avatar]}>
-          <UserAvatar
-            avatar={opts.authorAvatar}
-            handle={opts.authorHandle}
-            displayName={opts.authorDisplayName}
-            size={16}
-          />
+          <UserAvatar avatar={opts.authorAvatar} size={16} />
         </View>
       )}
       <View style={[styles.metaItem, styles.maxWidth]}>
-        <Text
+        <DesktopWebTextLink
           type="lg-bold"
-          style={[pal.text]}
+          style={pal.text}
           numberOfLines={1}
-          lineHeight={1.2}>
-          {displayName}
-          {handle ? (
-            <Text type="md" style={[pal.textLight]}>
-              &nbsp;{handle}
-            </Text>
-          ) : undefined}
-        </Text>
+          lineHeight={1.2}
+          text={
+            <>
+              {displayName}
+              <Text type="md" style={[pal.textLight]}>
+                &nbsp;{handle}
+              </Text>
+            </>
+          }
+          href={`/profile/${opts.authorHandle}`}
+        />
       </View>
-      <Text type="md" style={[styles.metaItem, pal.textLight]} lineHeight={1.2}>
-        &middot; {ago(opts.timestamp)}
+      <Text type="md" style={pal.textLight} lineHeight={1.2}>
+        &middot;&nbsp;
       </Text>
+      <DesktopWebTextLink
+        type="md"
+        style={[styles.metaItem, pal.textLight]}
+        lineHeight={1.2}
+        text={ago(opts.timestamp)}
+        href={opts.postHref}
+      />
     </View>
   )
 })
@@ -125,6 +136,10 @@ const styles = StyleSheet.create({
     justifyContent: 'space-between',
     paddingBottom: 2,
   },
+  metaTwoLineTop: {
+    flexDirection: 'row',
+    alignItems: 'baseline',
+  },
   metaItem: {
     paddingRight: 5,
   },
diff --git a/src/view/com/util/PostMuted.tsx b/src/view/com/util/PostMuted.tsx
index d8573bd56..539a71ecf 100644
--- a/src/view/com/util/PostMuted.tsx
+++ b/src/view/com/util/PostMuted.tsx
@@ -7,7 +7,7 @@ import {Text} from './text/Text'
 export function PostMutedWrapper({
   isMuted,
   children,
-}: React.PropsWithChildren<{isMuted: boolean}>) {
+}: React.PropsWithChildren<{isMuted?: boolean}>) {
   const pal = usePalette('default')
   const [override, setOverride] = React.useState(false)
   if (!isMuted || override) {
diff --git a/src/view/com/util/UserAvatar.tsx b/src/view/com/util/UserAvatar.tsx
index d0d2c273b..2e0632521 100644
--- a/src/view/com/util/UserAvatar.tsx
+++ b/src/view/com/util/UserAvatar.tsx
@@ -1,6 +1,6 @@
 import React from 'react'
 import {StyleSheet, View} from 'react-native'
-import Svg, {Circle, Text, Defs, LinearGradient, Stop} from 'react-native-svg'
+import Svg, {Circle, Path} from 'react-native-svg'
 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
 import {IconProp} from '@fortawesome/fontawesome-svg-core'
 import {HighPriorityImage} from 'view/com/util/images/Image'
@@ -11,52 +11,48 @@ import {
   PickedMedia,
 } from '../../../lib/media/picker'
 import {
-  requestPhotoAccessIfNeeded,
-  requestCameraAccessIfNeeded,
-} from 'lib/permissions'
+  usePhotoLibraryPermission,
+  useCameraPermission,
+} from 'lib/hooks/usePermissions'
 import {useStores} from 'state/index'
-import {colors, gradients} from 'lib/styles'
+import {colors} from 'lib/styles'
 import {DropdownButton} from './forms/DropdownButton'
 import {usePalette} from 'lib/hooks/usePalette'
 import {isWeb} from 'platform/detection'
 
+function DefaultAvatar({size}: {size: number}) {
+  return (
+    <Svg
+      width={size}
+      height={size}
+      viewBox="0 0 24 24"
+      fill="none"
+      stroke="none">
+      <Circle cx="12" cy="12" r="12" fill="#0070ff" />
+      <Circle cx="12" cy="9.5" r="3.5" fill="#fff" />
+      <Path
+        strokeLinecap="round"
+        strokeLinejoin="round"
+        fill="#fff"
+        d="M 12.058 22.784 C 9.422 22.784 7.007 21.836 5.137 20.262 C 5.667 17.988 8.534 16.25 11.99 16.25 C 15.494 16.25 18.391 18.036 18.864 20.357 C 17.01 21.874 14.64 22.784 12.058 22.784 Z"
+      />
+    </Svg>
+  )
+}
+
 export function UserAvatar({
   size,
-  handle,
   avatar,
-  displayName,
   onSelectNewAvatar,
 }: {
   size: number
-  handle: string
-  displayName: string | undefined
   avatar?: string | null
   onSelectNewAvatar?: (img: PickedMedia | null) => void
 }) {
   const store = useStores()
   const pal = usePalette('default')
-  const initials = getInitials(displayName || handle)
-
-  const renderSvg = (svgSize: number, svgInitials: string) => (
-    <Svg width={svgSize} height={svgSize} viewBox="0 0 100 100">
-      <Defs>
-        <LinearGradient id="grad" x1="0" y1="0" x2="1" y2="1">
-          <Stop offset="0" stopColor={gradients.blue.start} stopOpacity="1" />
-          <Stop offset="1" stopColor={gradients.blue.end} stopOpacity="1" />
-        </LinearGradient>
-      </Defs>
-      <Circle cx="50" cy="50" r="50" fill="url(#grad)" />
-      <Text
-        fill="white"
-        fontSize="50"
-        fontWeight="bold"
-        x="50"
-        y="67"
-        textAnchor="middle">
-        {svgInitials}
-      </Text>
-    </Svg>
-  )
+  const {requestCameraAccessIfNeeded} = useCameraPermission()
+  const {requestPhotoAccessIfNeeded} = usePhotoLibraryPermission()
 
   const dropdownItems = [
     !isWeb && {
@@ -124,7 +120,7 @@ export function UserAvatar({
           source={{uri: avatar}}
         />
       ) : (
-        renderSvg(size, initials)
+        <DefaultAvatar size={size} />
       )}
       <View style={[styles.editButtonContainer, pal.btn]}>
         <FontAwesomeIcon
@@ -141,26 +137,10 @@ export function UserAvatar({
       source={{uri: avatar}}
     />
   ) : (
-    renderSvg(size, initials)
+    <DefaultAvatar size={size} />
   )
 }
 
-function getInitials(str: string): string {
-  const tokens = str
-    .toLowerCase()
-    .replace(/[^a-z]/g, '')
-    .split(' ')
-    .filter(Boolean)
-    .map(v => v.trim())
-  if (tokens.length >= 2 && tokens[0][0] && tokens[0][1]) {
-    return tokens[0][0].toUpperCase() + tokens[1][0].toUpperCase()
-  }
-  if (tokens.length === 1 && tokens[0][0]) {
-    return tokens[0][0].toUpperCase()
-  }
-  return 'X'
-}
-
 const styles = StyleSheet.create({
   editButtonContainer: {
     position: 'absolute',
diff --git a/src/view/com/util/UserBanner.tsx b/src/view/com/util/UserBanner.tsx
index 16e05311b..d89de9158 100644
--- a/src/view/com/util/UserBanner.tsx
+++ b/src/view/com/util/UserBanner.tsx
@@ -1,10 +1,10 @@
 import React from 'react'
 import {StyleSheet, View} from 'react-native'
-import Svg, {Rect, Defs, LinearGradient, Stop} from 'react-native-svg'
+import Svg, {Rect} from 'react-native-svg'
 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
 import {IconProp} from '@fortawesome/fontawesome-svg-core'
 import Image from 'view/com/util/images/Image'
-import {colors, gradients} from 'lib/styles'
+import {colors} from 'lib/styles'
 import {
   openCamera,
   openCropper,
@@ -13,9 +13,9 @@ import {
 } from '../../../lib/media/picker'
 import {useStores} from 'state/index'
 import {
-  requestPhotoAccessIfNeeded,
-  requestCameraAccessIfNeeded,
-} from 'lib/permissions'
+  usePhotoLibraryPermission,
+  useCameraPermission,
+} from 'lib/hooks/usePermissions'
 import {DropdownButton} from './forms/DropdownButton'
 import {usePalette} from 'lib/hooks/usePalette'
 import {isWeb} from 'platform/detection'
@@ -29,6 +29,9 @@ export function UserBanner({
 }) {
   const store = useStores()
   const pal = usePalette('default')
+  const {requestCameraAccessIfNeeded} = useCameraPermission()
+  const {requestPhotoAccessIfNeeded} = usePhotoLibraryPermission()
+
   const dropdownItems = [
     !isWeb && {
       label: 'Camera',
@@ -80,19 +83,8 @@ export function UserBanner({
   ]
 
   const renderSvg = () => (
-    <Svg width="100%" height="150" viewBox="50 0 200 100">
-      <Defs>
-        <LinearGradient id="grad" x1="0" y1="0" x2="1" y2="1">
-          <Stop
-            offset="0"
-            stopColor={gradients.blueDark.start}
-            stopOpacity="1"
-          />
-          <Stop offset="1" stopColor={gradients.blueDark.end} stopOpacity="1" />
-        </LinearGradient>
-      </Defs>
-      <Rect x="0" y="0" width="400" height="100" fill="url(#grad)" />
-      <Rect x="0" y="0" width="400" height="100" fill="url(#grad2)" />
+    <Svg width="100%" height="150" viewBox="0 0 400 100">
+      <Rect x="0" y="0" width="400" height="100" fill="#0070ff" />
     </Svg>
   )
 
diff --git a/src/view/com/util/UserInfoText.tsx b/src/view/com/util/UserInfoText.tsx
index 84170b3bf..4753c9b01 100644
--- a/src/view/com/util/UserInfoText.tsx
+++ b/src/view/com/util/UserInfoText.tsx
@@ -1,7 +1,7 @@
 import React, {useState, useEffect} from 'react'
 import {AppBskyActorGetProfile as GetProfile} from '@atproto/api'
 import {StyleProp, StyleSheet, TextStyle} from 'react-native'
-import {Link} from './Link'
+import {DesktopWebTextLink} from './Link'
 import {Text} from './text/Text'
 import {LoadingPlaceholder} from './LoadingPlaceholder'
 import {useStores} from 'state/index'
@@ -14,7 +14,6 @@ export function UserInfoText({
   failed,
   prefix,
   style,
-  asLink,
 }: {
   type?: TypographyVariant
   did: string
@@ -23,7 +22,6 @@ export function UserInfoText({
   failed?: string
   prefix?: string
   style?: StyleProp<TextStyle>
-  asLink?: boolean
 }) {
   attr = attr || 'handle'
   failed = failed || 'user'
@@ -64,9 +62,14 @@ export function UserInfoText({
     )
   } else if (profile) {
     inner = (
-      <Text type={type} style={style} lineHeight={1.2} numberOfLines={1}>{`${
-        prefix || ''
-      }${profile[attr] || profile.handle}`}</Text>
+      <DesktopWebTextLink
+        type={type}
+        style={style}
+        lineHeight={1.2}
+        numberOfLines={1}
+        href={`/profile/${profile.handle}`}
+        text={`${prefix || ''}${profile[attr] || profile.handle}`}
+      />
     )
   } else {
     inner = (
@@ -78,17 +81,6 @@ export function UserInfoText({
     )
   }
 
-  if (asLink) {
-    const title = profile?.displayName || profile?.handle || 'User'
-    return (
-      <Link
-        href={`/profile/${profile?.handle ? profile.handle : did}`}
-        title={title}>
-        {inner}
-      </Link>
-    )
-  }
-
   return inner
 }
 
diff --git a/src/view/com/util/ViewHeader.tsx b/src/view/com/util/ViewHeader.tsx
index ffd1b1d63..a99282512 100644
--- a/src/view/com/util/ViewHeader.tsx
+++ b/src/view/com/util/ViewHeader.tsx
@@ -2,17 +2,19 @@ import React from 'react'
 import {observer} from 'mobx-react-lite'
 import {Animated, StyleSheet, TouchableOpacity, View} from 'react-native'
 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
+import {useNavigation} from '@react-navigation/native'
 import {UserAvatar} from './UserAvatar'
 import {Text} from './text/Text'
 import {useStores} from 'state/index'
 import {usePalette} from 'lib/hooks/usePalette'
 import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
 import {useAnalytics} from 'lib/analytics'
-import {isDesktopWeb} from '../../../platform/detection'
+import {NavigationProp} from 'lib/routes/types'
+import {isDesktopWeb} from 'platform/detection'
 
 const BACK_HITSLOP = {left: 20, top: 20, right: 50, bottom: 20}
 
-export const ViewHeader = observer(function ViewHeader({
+export const ViewHeader = observer(function ({
   title,
   canGoBack,
   hideOnScroll,
@@ -23,50 +25,55 @@ export const ViewHeader = observer(function ViewHeader({
 }) {
   const pal = usePalette('default')
   const store = useStores()
+  const navigation = useNavigation<NavigationProp>()
   const {track} = useAnalytics()
-  const onPressBack = () => {
-    store.nav.tab.goBack()
-  }
-  const onPressMenu = () => {
+
+  const onPressBack = React.useCallback(() => {
+    if (navigation.canGoBack()) {
+      navigation.goBack()
+    } else {
+      navigation.navigate('Home')
+    }
+  }, [navigation])
+
+  const onPressMenu = React.useCallback(() => {
     track('ViewHeader:MenuButtonClicked')
-    store.shell.setMainMenuOpen(true)
-  }
-  if (typeof canGoBack === 'undefined') {
-    canGoBack = store.nav.tab.canGoBack
-  }
+    store.shell.openDrawer()
+  }, [track, store])
+
   if (isDesktopWeb) {
     return <></>
+  } else {
+    if (typeof canGoBack === 'undefined') {
+      canGoBack = navigation.canGoBack()
+    }
+
+    return (
+      <Container hideOnScroll={hideOnScroll || false}>
+        <TouchableOpacity
+          testID="viewHeaderBackOrMenuBtn"
+          onPress={canGoBack ? onPressBack : onPressMenu}
+          hitSlop={BACK_HITSLOP}
+          style={canGoBack ? styles.backBtn : styles.backBtnWide}>
+          {canGoBack ? (
+            <FontAwesomeIcon
+              size={18}
+              icon="angle-left"
+              style={[styles.backIcon, pal.text]}
+            />
+          ) : (
+            <UserAvatar size={30} avatar={store.me.avatar} />
+          )}
+        </TouchableOpacity>
+        <View style={styles.titleContainer} pointerEvents="none">
+          <Text type="title" style={[pal.text, styles.title]}>
+            {title}
+          </Text>
+        </View>
+        <View style={canGoBack ? styles.backBtn : styles.backBtnWide} />
+      </Container>
+    )
   }
-  return (
-    <Container hideOnScroll={hideOnScroll || false}>
-      <TouchableOpacity
-        testID="viewHeaderBackOrMenuBtn"
-        onPress={canGoBack ? onPressBack : onPressMenu}
-        hitSlop={BACK_HITSLOP}
-        style={canGoBack ? styles.backBtn : styles.backBtnWide}>
-        {canGoBack ? (
-          <FontAwesomeIcon
-            size={18}
-            icon="angle-left"
-            style={[styles.backIcon, pal.text]}
-          />
-        ) : (
-          <UserAvatar
-            size={30}
-            handle={store.me.handle}
-            displayName={store.me.displayName}
-            avatar={store.me.avatar}
-          />
-        )}
-      </TouchableOpacity>
-      <View style={styles.titleContainer} pointerEvents="none">
-        <Text type="title" style={[pal.text, styles.title]}>
-          {title}
-        </Text>
-      </View>
-      <View style={canGoBack ? styles.backBtn : styles.backBtnWide} />
-    </Container>
-  )
 })
 
 const Container = observer(
@@ -119,8 +126,7 @@ const styles = StyleSheet.create({
     flexDirection: 'row',
     alignItems: 'center',
     paddingHorizontal: 12,
-    paddingTop: 6,
-    paddingBottom: 6,
+    paddingVertical: 6,
   },
   headerFloating: {
     position: 'absolute',
diff --git a/src/view/com/util/Views.web.tsx b/src/view/com/util/Views.web.tsx
index 8b5adaa04..9a43697b5 100644
--- a/src/view/com/util/Views.web.tsx
+++ b/src/view/com/util/Views.web.tsx
@@ -23,7 +23,6 @@ import {
   ViewProps,
 } from 'react-native'
 import {addStyle, colors} from 'lib/styles'
-import {DESKTOP_HEADER_HEIGHT} from 'lib/constants'
 
 export function CenteredView({
   style,
@@ -73,14 +72,14 @@ export const ScrollView = React.forwardRef(function (
 const styles = StyleSheet.create({
   container: {
     width: '100%',
-    maxWidth: 550,
+    maxWidth: 600,
     marginLeft: 'auto',
     marginRight: 'auto',
   },
   containerScroll: {
     width: '100%',
-    height: `calc(100vh - ${DESKTOP_HEADER_HEIGHT}px)`,
-    maxWidth: 550,
+    minHeight: '100vh',
+    maxWidth: 600,
     marginLeft: 'auto',
     marginRight: 'auto',
   },
diff --git a/src/view/com/util/forms/DropdownButton.tsx b/src/view/com/util/forms/DropdownButton.tsx
index ac83d1a54..d6ae800c6 100644
--- a/src/view/com/util/forms/DropdownButton.tsx
+++ b/src/view/com/util/forms/DropdownButton.tsx
@@ -17,7 +17,6 @@ import {Button, ButtonType} from './Button'
 import {colors} from 'lib/styles'
 import {toShareUrl} from 'lib/strings/url-helpers'
 import {useStores} from 'state/index'
-import {TABS_ENABLED} from 'lib/build-flags'
 import {usePalette} from 'lib/hooks/usePalette'
 import {useTheme} from 'lib/ThemeContext'
 
@@ -138,15 +137,6 @@ export function PostDropdownBtn({
   const store = useStores()
 
   const dropdownItems: DropdownItem[] = [
-    TABS_ENABLED
-      ? {
-          icon: ['far', 'clone'],
-          label: 'Open in new tab',
-          onPress() {
-            store.nav.newTab(itemHref)
-          },
-        }
-      : undefined,
     {
       icon: 'language',
       label: 'Translate...',
diff --git a/src/view/com/util/forms/RadioButton.tsx b/src/view/com/util/forms/RadioButton.tsx
index 57a875cd3..d6b2bb119 100644
--- a/src/view/com/util/forms/RadioButton.tsx
+++ b/src/view/com/util/forms/RadioButton.tsx
@@ -41,6 +41,9 @@ export function RadioButton({
     'secondary-light': {
       borderColor: theme.palette.secondary.border,
     },
+    default: {
+      borderColor: theme.palette.default.border,
+    },
     'default-light': {
       borderColor: theme.palette.default.border,
     },
@@ -69,6 +72,9 @@ export function RadioButton({
       'secondary-light': {
         backgroundColor: theme.palette.secondary.background,
       },
+      default: {
+        backgroundColor: theme.palette.primary.background,
+      },
       'default-light': {
         backgroundColor: theme.palette.primary.background,
       },
@@ -103,6 +109,10 @@ export function RadioButton({
       color: theme.palette.secondary.textInverted,
       fontWeight: theme.palette.secondary.isLowContrast ? '500' : undefined,
     },
+    default: {
+      color: theme.palette.default.text,
+      fontWeight: theme.palette.default.isLowContrast ? '500' : undefined,
+    },
     'default-light': {
       color: theme.palette.default.text,
       fontWeight: theme.palette.default.isLowContrast ? '500' : undefined,
diff --git a/src/view/com/util/forms/ToggleButton.tsx b/src/view/com/util/forms/ToggleButton.tsx
index 005d1165e..a6e0ba3fe 100644
--- a/src/view/com/util/forms/ToggleButton.tsx
+++ b/src/view/com/util/forms/ToggleButton.tsx
@@ -42,6 +42,9 @@ export function ToggleButton({
     'secondary-light': {
       borderColor: theme.palette.secondary.border,
     },
+    default: {
+      borderColor: theme.palette.default.border,
+    },
     'default-light': {
       borderColor: theme.palette.default.border,
     },
@@ -77,6 +80,11 @@ export function ToggleButton({
         backgroundColor: theme.palette.secondary.background,
         opacity: isSelected ? 1 : 0.5,
       },
+      default: {
+        backgroundColor: isSelected
+          ? theme.palette.primary.background
+          : colors.gray3,
+      },
       'default-light': {
         backgroundColor: isSelected
           ? theme.palette.primary.background
@@ -113,6 +121,10 @@ export function ToggleButton({
       color: theme.palette.secondary.textInverted,
       fontWeight: theme.palette.secondary.isLowContrast ? '500' : undefined,
     },
+    default: {
+      color: theme.palette.default.text,
+      fontWeight: theme.palette.default.isLowContrast ? '500' : undefined,
+    },
     'default-light': {
       color: theme.palette.default.text,
       fontWeight: theme.palette.default.isLowContrast ? '500' : undefined,
diff --git a/src/view/routes.ts b/src/view/routes.ts
deleted file mode 100644
index 1cd9ef8e2..000000000
--- a/src/view/routes.ts
+++ /dev/null
@@ -1,91 +0,0 @@
-import React from 'react'
-import {IconProp} from '@fortawesome/fontawesome-svg-core'
-import {Home} from './screens/Home'
-import {Contacts} from './screens/Contacts'
-import {Search} from './screens/Search'
-import {Notifications} from './screens/Notifications'
-import {NotFound} from './screens/NotFound'
-import {PostThread} from './screens/PostThread'
-import {PostUpvotedBy} from './screens/PostUpvotedBy'
-import {PostDownvotedBy} from './screens/PostDownvotedBy'
-import {PostRepostedBy} from './screens/PostRepostedBy'
-import {Profile} from './screens/Profile'
-import {ProfileFollowers} from './screens/ProfileFollowers'
-import {ProfileFollows} from './screens/ProfileFollows'
-import {Settings} from './screens/Settings'
-import {Debug} from './screens/Debug'
-import {Log} from './screens/Log'
-
-export type ScreenParams = {
-  navIdx: string
-  params: Record<string, any>
-  visible: boolean
-}
-export type Route = [React.FC<ScreenParams>, string, IconProp, RegExp]
-export type MatchResult = {
-  Com: React.FC<ScreenParams>
-  defaultTitle: string
-  icon: IconProp
-  params: Record<string, any>
-  isNotFound?: boolean
-}
-
-const r = (pattern: string) => new RegExp('^' + pattern + '([?]|$)', 'i')
-export const routes: Route[] = [
-  [Home, 'Home', 'house', r('/')],
-  [Contacts, 'Contacts', ['far', 'circle-user'], r('/contacts')],
-  [Search, 'Search', 'magnifying-glass', r('/search')],
-  [Notifications, 'Notifications', 'bell', r('/notifications')],
-  [Settings, 'Settings', 'bell', r('/settings')],
-  [Profile, 'User', ['far', 'user'], r('/profile/(?<name>[^/]+)')],
-  [
-    ProfileFollowers,
-    'Followers',
-    'users',
-    r('/profile/(?<name>[^/]+)/followers'),
-  ],
-  [ProfileFollows, 'Follows', 'users', r('/profile/(?<name>[^/]+)/follows')],
-  [
-    PostThread,
-    'Post',
-    ['far', 'message'],
-    r('/profile/(?<name>[^/]+)/post/(?<rkey>[^/]+)'),
-  ],
-  [
-    PostUpvotedBy,
-    'Liked by',
-    'heart',
-    r('/profile/(?<name>[^/]+)/post/(?<rkey>[^/]+)/upvoted-by'),
-  ],
-  [
-    PostDownvotedBy,
-    'Downvoted by',
-    'heart',
-    r('/profile/(?<name>[^/]+)/post/(?<rkey>[^/]+)/downvoted-by'),
-  ],
-  [
-    PostRepostedBy,
-    'Reposted by',
-    'retweet',
-    r('/profile/(?<name>[^/]+)/post/(?<rkey>[^/]+)/reposted-by'),
-  ],
-  [Debug, 'Debug', 'house', r('/sys/debug')],
-  [Log, 'Log', 'house', r('/sys/log')],
-]
-
-export function match(url: string): MatchResult {
-  for (const [Com, defaultTitle, icon, pattern] of routes) {
-    const res = pattern.exec(url)
-    if (res) {
-      // TODO: query params
-      return {Com, defaultTitle, icon, params: res.groups || {}}
-    }
-  }
-  return {
-    Com: NotFound,
-    defaultTitle: 'Not found',
-    icon: 'magnifying-glass',
-    params: {},
-    isNotFound: true,
-  }
-}
diff --git a/src/view/screens/Contacts.tsx b/src/view/screens/Contacts.tsx
deleted file mode 100644
index 21943a10a..000000000
--- a/src/view/screens/Contacts.tsx
+++ /dev/null
@@ -1,88 +0,0 @@
-import React, {useEffect, useState, useRef} from 'react'
-import {StyleSheet, TextInput, View} from 'react-native'
-import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
-import {ProfileFollows as ProfileFollowsComponent} from '../com/profile/ProfileFollows'
-import {Selector} from '../com/util/Selector'
-import {Text} from '../com/util/text/Text'
-import {colors} from 'lib/styles'
-import {ScreenParams} from '../routes'
-import {useStores} from 'state/index'
-import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
-
-export const Contacts = ({navIdx, visible}: ScreenParams) => {
-  const store = useStores()
-  const selectorInterp = useAnimatedValue(0)
-
-  useEffect(() => {
-    if (visible) {
-      store.nav.setTitle(navIdx, 'Contacts')
-    }
-  }, [store, visible, navIdx])
-
-  const [searchText, onChangeSearchText] = useState('')
-  const inputRef = useRef<TextInput | null>(null)
-
-  return (
-    <View>
-      <View style={styles.section}>
-        <Text testID="contactsTitle" style={styles.title}>
-          Contacts
-        </Text>
-      </View>
-      <View style={styles.section}>
-        <View style={styles.searchContainer}>
-          <FontAwesomeIcon
-            icon="magnifying-glass"
-            size={16}
-            style={styles.searchIcon}
-          />
-          <TextInput
-            testID="contactsTextInput"
-            ref={inputRef}
-            value={searchText}
-            style={styles.searchInput}
-            placeholder="Search"
-            placeholderTextColor={colors.gray4}
-            onChangeText={onChangeSearchText}
-          />
-        </View>
-      </View>
-      <Selector
-        items={['All', 'Following', 'Scenes']}
-        selectedIndex={0}
-        panX={selectorInterp}
-      />
-      {!!store.me.handle && <ProfileFollowsComponent name={store.me.handle} />}
-    </View>
-  )
-}
-
-const styles = StyleSheet.create({
-  section: {
-    backgroundColor: colors.white,
-  },
-  title: {
-    fontSize: 30,
-    fontWeight: 'bold',
-    paddingHorizontal: 12,
-    paddingVertical: 6,
-  },
-
-  searchContainer: {
-    flexDirection: 'row',
-    backgroundColor: colors.gray1,
-    paddingHorizontal: 8,
-    paddingVertical: 8,
-    marginHorizontal: 10,
-    marginBottom: 6,
-    borderRadius: 4,
-  },
-  searchIcon: {
-    color: colors.gray5,
-    marginRight: 8,
-  },
-  searchInput: {
-    flex: 1,
-    color: colors.black,
-  },
-})
diff --git a/src/view/screens/Debug.tsx b/src/view/screens/Debug.tsx
index eb5ffe20f..852025324 100644
--- a/src/view/screens/Debug.tsx
+++ b/src/view/screens/Debug.tsx
@@ -1,5 +1,6 @@
 import React from 'react'
 import {ScrollView, View} from 'react-native'
+import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
 import {ViewHeader} from '../com/util/ViewHeader'
 import {ThemeProvider, PaletteColorName} from 'lib/ThemeContext'
 import {usePalette} from 'lib/hooks/usePalette'
@@ -20,7 +21,10 @@ import {ErrorMessage} from '../com/util/error/ErrorMessage'
 
 const MAIN_VIEWS = ['Base', 'Controls', 'Error', 'Notifs']
 
-export const Debug = () => {
+export const DebugScreen = ({}: NativeStackScreenProps<
+  CommonNavigatorParams,
+  'Debug'
+>) => {
   const [colorScheme, setColorScheme] = React.useState<'light' | 'dark'>(
     'light',
   )
diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx
index 42759f7ff..505b1fcfe 100644
--- a/src/view/screens/Home.tsx
+++ b/src/view/screens/Home.tsx
@@ -1,14 +1,15 @@
 import React from 'react'
 import {FlatList, View} from 'react-native'
+import {useFocusEffect, useIsFocused} from '@react-navigation/native'
 import {observer} from 'mobx-react-lite'
 import useAppState from 'react-native-appstate-hook'
+import {NativeStackScreenProps, HomeTabNavigatorParams} from 'lib/routes/types'
 import {ViewHeader} from '../com/util/ViewHeader'
 import {Feed} from '../com/posts/Feed'
 import {LoadLatestBtn} from '../com/util/LoadLatestBtn'
 import {WelcomeBanner} from '../com/util/WelcomeBanner'
 import {FAB} from '../com/util/FAB'
 import {useStores} from 'state/index'
-import {ScreenParams} from '../routes'
 import {s} from 'lib/styles'
 import {useOnMainScroll} from 'lib/hooks/useOnMainScroll'
 import {useAnalytics} from 'lib/analytics'
@@ -16,19 +17,20 @@ import {ComposeIcon2} from 'lib/icons'
 
 const HEADER_HEIGHT = 42
 
-export const Home = observer(function Home({navIdx, visible}: ScreenParams) {
+type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home'>
+export const HomeScreen = observer(function Home(_opts: Props) {
   const store = useStores()
   const onMainScroll = useOnMainScroll(store)
   const {screen, track} = useAnalytics()
   const scrollElRef = React.useRef<FlatList>(null)
-  const [wasVisible, setWasVisible] = React.useState<boolean>(false)
   const {appState} = useAppState({
     onForeground: () => doPoll(true),
   })
+  const isFocused = useIsFocused()
 
   const doPoll = React.useCallback(
     (knownActive = false) => {
-      if ((!knownActive && appState !== 'active') || !visible) {
+      if ((!knownActive && appState !== 'active') || !isFocused) {
         return
       }
       if (store.me.mainFeed.isLoading) {
@@ -37,7 +39,7 @@ export const Home = observer(function Home({navIdx, visible}: ScreenParams) {
       store.log.debug('HomeScreen: Polling for new posts')
       store.me.mainFeed.checkForLatest()
     },
-    [appState, visible, store],
+    [appState, isFocused, store],
   )
 
   const scrollToTop = React.useCallback(() => {
@@ -46,53 +48,35 @@ export const Home = observer(function Home({navIdx, visible}: ScreenParams) {
     scrollElRef.current?.scrollToOffset({offset: -HEADER_HEIGHT})
   }, [scrollElRef])
 
-  React.useEffect(() => {
-    const softResetSub = store.onScreenSoftReset(scrollToTop)
-    const feedCleanup = store.me.mainFeed.registerListeners()
-    const pollInterval = setInterval(doPoll, 15e3)
-    const cleanup = () => {
-      clearInterval(pollInterval)
-      softResetSub.remove()
-      feedCleanup()
-    }
+  useFocusEffect(
+    React.useCallback(() => {
+      const softResetSub = store.onScreenSoftReset(scrollToTop)
+      const feedCleanup = store.me.mainFeed.registerListeners()
+      const pollInterval = setInterval(doPoll, 15e3)
 
-    // guard to only continue when transitioning from !visible -> visible
-    // TODO is this 100% needed? depends on if useEffect() is getting refired
-    //      for reasons other than `visible` changing -prf
-    if (!visible) {
-      setWasVisible(false)
-      return cleanup
-    } else if (wasVisible) {
-      return cleanup
-    }
-    setWasVisible(true)
+      screen('Feed')
+      store.log.debug('HomeScreen: Updating feed')
+      if (store.me.mainFeed.hasContent) {
+        store.me.mainFeed.update()
+      }
 
-    // just became visible
-    screen('Feed')
-    store.nav.setTitle(navIdx, 'Home')
-    store.log.debug('HomeScreen: Updating feed')
-    if (store.me.mainFeed.hasContent) {
-      store.me.mainFeed.update()
-    }
-    return cleanup
-  }, [
-    visible,
-    store,
-    store.me.mainFeed,
-    navIdx,
-    doPoll,
-    wasVisible,
-    scrollToTop,
-    screen,
-  ])
+      return () => {
+        clearInterval(pollInterval)
+        softResetSub.remove()
+        feedCleanup()
+      }
+    }, [store, doPoll, scrollToTop, screen]),
+  )
 
   const onPressCompose = React.useCallback(() => {
     track('HomeScreen:PressCompose')
     store.shell.openComposer({})
   }, [store, track])
+
   const onPressTryAgain = React.useCallback(() => {
     store.me.mainFeed.refresh()
   }, [store])
+
   const onPressLoadLatest = React.useCallback(() => {
     store.me.mainFeed.refresh()
     scrollToTop()
diff --git a/src/view/screens/Log.tsx b/src/view/screens/Log.tsx
index c067d3506..8e0fe8dd3 100644
--- a/src/view/screens/Log.tsx
+++ b/src/view/screens/Log.tsx
@@ -1,28 +1,30 @@
-import React, {useEffect} from 'react'
+import React from 'react'
 import {StyleSheet, TouchableOpacity, View} from 'react-native'
+import {useFocusEffect} from '@react-navigation/native'
 import {observer} from 'mobx-react-lite'
 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
+import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
 import {ScrollView} from '../com/util/Views'
 import {useStores} from 'state/index'
-import {ScreenParams} from '../routes'
 import {s} from 'lib/styles'
 import {ViewHeader} from '../com/util/ViewHeader'
 import {Text} from '../com/util/text/Text'
 import {usePalette} from 'lib/hooks/usePalette'
 import {ago} from 'lib/strings/time'
 
-export const Log = observer(function Log({navIdx, visible}: ScreenParams) {
+export const LogScreen = observer(function Log({}: NativeStackScreenProps<
+  CommonNavigatorParams,
+  'Log'
+>) {
   const pal = usePalette('default')
   const store = useStores()
   const [expanded, setExpanded] = React.useState<string[]>([])
 
-  useEffect(() => {
-    if (!visible) {
-      return
-    }
-    store.shell.setMinimalShellMode(false)
-    store.nav.setTitle(navIdx, 'Log')
-  }, [visible, store, navIdx])
+  useFocusEffect(
+    React.useCallback(() => {
+      store.shell.setMinimalShellMode(false)
+    }, [store]),
+  )
 
   const toggler = (id: string) => () => {
     if (expanded.includes(id)) {
diff --git a/src/view/screens/NotFound.tsx b/src/view/screens/NotFound.tsx
index 77bbdd2aa..6ab37f117 100644
--- a/src/view/screens/NotFound.tsx
+++ b/src/view/screens/NotFound.tsx
@@ -1,20 +1,41 @@
 import React from 'react'
-import {Button, StyleSheet, View} from 'react-native'
+import {StyleSheet, View} from 'react-native'
+import {useNavigation, StackActions} from '@react-navigation/native'
 import {ViewHeader} from '../com/util/ViewHeader'
 import {Text} from '../com/util/text/Text'
-import {useStores} from 'state/index'
+import {Button} from 'view/com/util/forms/Button'
+import {NavigationProp} from 'lib/routes/types'
+import {usePalette} from 'lib/hooks/usePalette'
+import {s} from 'lib/styles'
+
+export const NotFoundScreen = () => {
+  const pal = usePalette('default')
+  const navigation = useNavigation<NavigationProp>()
+
+  const canGoBack = navigation.canGoBack()
+  const onPressHome = React.useCallback(() => {
+    if (canGoBack) {
+      navigation.goBack()
+    } else {
+      navigation.navigate('HomeTab')
+      navigation.dispatch(StackActions.popToTop())
+    }
+  }, [navigation, canGoBack])
 
-export const NotFound = () => {
-  const stores = useStores()
   return (
-    <View testID="notFoundView">
+    <View testID="notFoundView" style={pal.view}>
       <ViewHeader title="Page not found" />
       <View style={styles.container}>
-        <Text style={styles.title}>Page not found</Text>
+        <Text type="title-2xl" style={[pal.text, s.mb10]}>
+          Page not found
+        </Text>
+        <Text type="md" style={[pal.text, s.mb10]}>
+          We're sorry! We can't find the page you were looking for.
+        </Text>
         <Button
-          testID="navigateHomeButton"
-          title="Home"
-          onPress={() => stores.nav.navigate('/')}
+          type="primary"
+          label={canGoBack ? 'Go back' : 'Go home'}
+          onPress={onPressHome}
         />
       </View>
     </View>
@@ -23,12 +44,9 @@ export const NotFound = () => {
 
 const styles = StyleSheet.create({
   container: {
-    justifyContent: 'center',
-    alignItems: 'center',
     paddingTop: 100,
-  },
-  title: {
-    fontSize: 40,
-    fontWeight: 'bold',
+    paddingHorizontal: 20,
+    alignItems: 'center',
+    height: '100%',
   },
 })
diff --git a/src/view/screens/Notifications.tsx b/src/view/screens/Notifications.tsx
index f1a9e8bf0..492177d1f 100644
--- a/src/view/screens/Notifications.tsx
+++ b/src/view/screens/Notifications.tsx
@@ -1,17 +1,25 @@
 import React, {useEffect} from 'react'
 import {FlatList, View} from 'react-native'
+import {useFocusEffect} from '@react-navigation/native'
 import useAppState from 'react-native-appstate-hook'
+import {
+  NativeStackScreenProps,
+  NotificationsTabNavigatorParams,
+} from 'lib/routes/types'
 import {ViewHeader} from '../com/util/ViewHeader'
 import {Feed} from '../com/notifications/Feed'
 import {useStores} from 'state/index'
-import {ScreenParams} from '../routes'
 import {useOnMainScroll} from 'lib/hooks/useOnMainScroll'
 import {s} from 'lib/styles'
 import {useAnalytics} from 'lib/analytics'
 
 const NOTIFICATIONS_POLL_INTERVAL = 15e3
 
-export const Notifications = ({navIdx, visible}: ScreenParams) => {
+type Props = NativeStackScreenProps<
+  NotificationsTabNavigatorParams,
+  'Notifications'
+>
+export const NotificationsScreen = ({}: Props) => {
   const store = useStores()
   const onMainScroll = useOnMainScroll(store)
   const scrollElRef = React.useRef<FlatList>(null)
@@ -59,21 +67,19 @@ export const Notifications = ({navIdx, visible}: ScreenParams) => {
 
   // on-visible setup
   // =
-  useEffect(() => {
-    if (!visible) {
-      // mark read when the user leaves the screen
-      store.me.notifications.markAllRead()
-      return
-    }
-    store.log.debug('NotificationsScreen: Updating feed')
-    const softResetSub = store.onScreenSoftReset(scrollToTop)
-    store.me.notifications.update()
-    screen('Notifications')
-    store.nav.setTitle(navIdx, 'Notifications')
-    return () => {
-      softResetSub.remove()
-    }
-  }, [visible, store, navIdx, screen, scrollToTop])
+  useFocusEffect(
+    React.useCallback(() => {
+      store.log.debug('NotificationsScreen: Updating feed')
+      const softResetSub = store.onScreenSoftReset(scrollToTop)
+      store.me.notifications.update()
+      screen('Notifications')
+
+      return () => {
+        softResetSub.remove()
+        store.me.notifications.markAllRead()
+      }
+    }, [store, screen, scrollToTop]),
+  )
 
   return (
     <View style={s.hContentRegion}>
diff --git a/src/view/screens/PostDownvotedBy.tsx b/src/view/screens/PostDownvotedBy.tsx
deleted file mode 100644
index 570482598..000000000
--- a/src/view/screens/PostDownvotedBy.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import React, {useEffect} from 'react'
-import {View} from 'react-native'
-import {ViewHeader} from '../com/util/ViewHeader'
-import {PostVotedBy as PostLikedByComponent} from '../com/post-thread/PostVotedBy'
-import {ScreenParams} from '../routes'
-import {useStores} from 'state/index'
-import {makeRecordUri} from 'lib/strings/url-helpers'
-
-export const PostDownvotedBy = ({navIdx, visible, params}: ScreenParams) => {
-  const store = useStores()
-  const {name, rkey} = params
-  const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey)
-
-  useEffect(() => {
-    if (visible) {
-      store.nav.setTitle(navIdx, 'Downvoted by')
-      store.shell.setMinimalShellMode(false)
-    }
-  }, [store, visible, navIdx])
-
-  return (
-    <View>
-      <ViewHeader title="Downvoted by" />
-      <PostLikedByComponent uri={uri} direction="down" />
-    </View>
-  )
-}
diff --git a/src/view/screens/PostRepostedBy.tsx b/src/view/screens/PostRepostedBy.tsx
index 4be4b4b42..1a63445e5 100644
--- a/src/view/screens/PostRepostedBy.tsx
+++ b/src/view/screens/PostRepostedBy.tsx
@@ -1,22 +1,23 @@
-import React, {useEffect} from 'react'
+import React from 'react'
 import {View} from 'react-native'
+import {useFocusEffect} from '@react-navigation/native'
+import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
 import {ViewHeader} from '../com/util/ViewHeader'
 import {PostRepostedBy as PostRepostedByComponent} from '../com/post-thread/PostRepostedBy'
-import {ScreenParams} from '../routes'
 import {useStores} from 'state/index'
 import {makeRecordUri} from 'lib/strings/url-helpers'
 
-export const PostRepostedBy = ({navIdx, visible, params}: ScreenParams) => {
+type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostRepostedBy'>
+export const PostRepostedByScreen = ({route}: Props) => {
   const store = useStores()
-  const {name, rkey} = params
+  const {name, rkey} = route.params
   const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey)
 
-  useEffect(() => {
-    if (visible) {
-      store.nav.setTitle(navIdx, 'Reposted by')
+  useFocusEffect(
+    React.useCallback(() => {
       store.shell.setMinimalShellMode(false)
-    }
-  }, [store, visible, navIdx])
+    }, [store]),
+  )
 
   return (
     <View>
diff --git a/src/view/screens/PostThread.tsx b/src/view/screens/PostThread.tsx
index 0b6829735..0e9feae0b 100644
--- a/src/view/screens/PostThread.tsx
+++ b/src/view/screens/PostThread.tsx
@@ -1,58 +1,45 @@
-import React, {useEffect, useMemo} from 'react'
+import React, {useMemo} from 'react'
 import {StyleSheet, View} from 'react-native'
+import {useFocusEffect} from '@react-navigation/native'
+import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
 import {makeRecordUri} from 'lib/strings/url-helpers'
 import {ViewHeader} from '../com/util/ViewHeader'
 import {PostThread as PostThreadComponent} from '../com/post-thread/PostThread'
 import {ComposePrompt} from 'view/com/composer/Prompt'
 import {PostThreadViewModel} from 'state/models/post-thread-view'
-import {ScreenParams} from '../routes'
 import {useStores} from 'state/index'
 import {s} from 'lib/styles'
 import {useSafeAreaInsets} from 'react-native-safe-area-context'
 import {clamp} from 'lodash'
+import {isDesktopWeb} from 'platform/detection'
 
 const SHELL_FOOTER_HEIGHT = 44
 
-export const PostThread = ({navIdx, visible, params}: ScreenParams) => {
+type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostThread'>
+export const PostThreadScreen = ({route}: Props) => {
   const store = useStores()
   const safeAreaInsets = useSafeAreaInsets()
-  const {name, rkey} = params
+  const {name, rkey} = route.params
   const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey)
   const view = useMemo<PostThreadViewModel>(
     () => new PostThreadViewModel(store, {uri}),
     [store, uri],
   )
 
-  useEffect(() => {
-    let aborted = false
-    const threadCleanup = view.registerListeners()
-    const setTitle = () => {
-      const author = view.thread?.post.author
-      const niceName = author?.handle || name
-      store.nav.setTitle(navIdx, `Post by ${niceName}`)
-    }
-    if (!visible) {
-      return threadCleanup
-    }
-    setTitle()
-    store.shell.setMinimalShellMode(false)
-    if (!view.hasLoaded && !view.isLoading) {
-      view.setup().then(
-        () => {
-          if (!aborted) {
-            setTitle()
-          }
-        },
-        err => {
+  useFocusEffect(
+    React.useCallback(() => {
+      const threadCleanup = view.registerListeners()
+      store.shell.setMinimalShellMode(false)
+      if (!view.hasLoaded && !view.isLoading) {
+        view.setup().catch(err => {
           store.log.error('Failed to fetch thread', err)
-        },
-      )
-    }
-    return () => {
-      aborted = true
-      threadCleanup()
-    }
-  }, [visible, store.nav, store.log, store.shell, name, navIdx, view])
+        })
+      }
+      return () => {
+        threadCleanup()
+      }
+    }, [store, view]),
+  )
 
   const onPressReply = React.useCallback(() => {
     if (!view.thread) {
@@ -77,15 +64,24 @@ export const PostThread = ({navIdx, visible, params}: ScreenParams) => {
     <View style={s.hContentRegion}>
       <ViewHeader title="Post" />
       <View style={s.hContentRegion}>
-        <PostThreadComponent uri={uri} view={view} />
-      </View>
-      <View
-        style={[
-          styles.prompt,
-          {bottom: SHELL_FOOTER_HEIGHT + clamp(safeAreaInsets.bottom, 15, 30)},
-        ]}>
-        <ComposePrompt onPressCompose={onPressReply} />
+        <PostThreadComponent
+          uri={uri}
+          view={view}
+          onPressReply={onPressReply}
+        />
       </View>
+      {!isDesktopWeb && (
+        <View
+          style={[
+            styles.prompt,
+            {
+              bottom:
+                SHELL_FOOTER_HEIGHT + clamp(safeAreaInsets.bottom, 15, 30),
+            },
+          ]}>
+          <ComposePrompt onPressCompose={onPressReply} />
+        </View>
+      )}
     </View>
   )
 }
diff --git a/src/view/screens/PostUpvotedBy.tsx b/src/view/screens/PostUpvotedBy.tsx
index 4d6ad4114..b1690721b 100644
--- a/src/view/screens/PostUpvotedBy.tsx
+++ b/src/view/screens/PostUpvotedBy.tsx
@@ -1,21 +1,23 @@
-import React, {useEffect} from 'react'
+import React from 'react'
 import {View} from 'react-native'
+import {useFocusEffect} from '@react-navigation/native'
+import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
 import {ViewHeader} from '../com/util/ViewHeader'
 import {PostVotedBy as PostLikedByComponent} from '../com/post-thread/PostVotedBy'
-import {ScreenParams} from '../routes'
 import {useStores} from 'state/index'
 import {makeRecordUri} from 'lib/strings/url-helpers'
 
-export const PostUpvotedBy = ({navIdx, visible, params}: ScreenParams) => {
+type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostUpvotedBy'>
+export const PostUpvotedByScreen = ({route}: Props) => {
   const store = useStores()
-  const {name, rkey} = params
+  const {name, rkey} = route.params
   const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey)
 
-  useEffect(() => {
-    if (visible) {
-      store.nav.setTitle(navIdx, 'Liked by')
-    }
-  }, [store, visible, navIdx])
+  useFocusEffect(
+    React.useCallback(() => {
+      store.shell.setMinimalShellMode(false)
+    }, [store]),
+  )
 
   return (
     <View>
diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx
index fa0c04106..e0d0a5884 100644
--- a/src/view/screens/Profile.tsx
+++ b/src/view/screens/Profile.tsx
@@ -1,9 +1,10 @@
 import React, {useEffect, useState} from 'react'
 import {ActivityIndicator, StyleSheet, View} from 'react-native'
 import {observer} from 'mobx-react-lite'
+import {useFocusEffect} from '@react-navigation/native'
+import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
 import {ViewSelector} from '../com/util/ViewSelector'
 import {CenteredView} from '../com/util/Views'
-import {ScreenParams} from '../routes'
 import {ProfileUiModel, Sections} from 'state/models/profile-ui'
 import {useStores} from 'state/index'
 import {ProfileHeader} from '../com/profile/ProfileHeader'
@@ -23,7 +24,8 @@ const LOADING_ITEM = {_reactKey: '__loading__'}
 const END_ITEM = {_reactKey: '__end__'}
 const EMPTY_ITEM = {_reactKey: '__empty__'}
 
-export const Profile = observer(({navIdx, visible, params}: ScreenParams) => {
+type Props = NativeStackScreenProps<CommonNavigatorParams, 'Profile'>
+export const ProfileScreen = observer(({route}: Props) => {
   const store = useStores()
   const {screen, track} = useAnalytics()
 
@@ -34,35 +36,30 @@ export const Profile = observer(({navIdx, visible, params}: ScreenParams) => {
   const onMainScroll = useOnMainScroll(store)
   const [hasSetup, setHasSetup] = useState<boolean>(false)
   const uiState = React.useMemo(
-    () => new ProfileUiModel(store, {user: params.name}),
-    [params.name, store],
+    () => new ProfileUiModel(store, {user: route.params.name}),
+    [route.params.name, store],
   )
 
-  useEffect(() => {
-    store.nav.setTitle(navIdx, params.name)
-  }, [store, navIdx, params.name])
-
-  useEffect(() => {
-    let aborted = false
-    const feedCleanup = uiState.feed.registerListeners()
-    if (!visible) {
-      return feedCleanup
-    }
-    if (hasSetup) {
-      uiState.update()
-    } else {
-      uiState.setup().then(() => {
-        if (aborted) {
-          return
-        }
-        setHasSetup(true)
-      })
-    }
-    return () => {
-      aborted = true
-      feedCleanup()
-    }
-  }, [visible, store, hasSetup, uiState])
+  useFocusEffect(
+    React.useCallback(() => {
+      let aborted = false
+      const feedCleanup = uiState.feed.registerListeners()
+      if (hasSetup) {
+        uiState.update()
+      } else {
+        uiState.setup().then(() => {
+          if (aborted) {
+            return
+          }
+          setHasSetup(true)
+        })
+      }
+      return () => {
+        aborted = true
+        feedCleanup()
+      }
+    }, [hasSetup, uiState]),
+  )
 
   // events
   // =
@@ -171,7 +168,7 @@ export const Profile = observer(({navIdx, visible, params}: ScreenParams) => {
         <ErrorScreen
           testID="profileErrorScreen"
           title="Failed to load profile"
-          message={`There was an issue when attempting to load ${params.name}`}
+          message={`There was an issue when attempting to load ${route.params.name}`}
           details={uiState.profile.error}
           onPressTryAgain={onPressTryAgain}
         />
diff --git a/src/view/screens/ProfileFollowers.tsx b/src/view/screens/ProfileFollowers.tsx
index 9f1a9c741..b248cdc3a 100644
--- a/src/view/screens/ProfileFollowers.tsx
+++ b/src/view/screens/ProfileFollowers.tsx
@@ -1,20 +1,21 @@
-import React, {useEffect} from 'react'
+import React from 'react'
 import {View} from 'react-native'
+import {useFocusEffect} from '@react-navigation/native'
+import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
 import {ViewHeader} from '../com/util/ViewHeader'
 import {ProfileFollowers as ProfileFollowersComponent} from '../com/profile/ProfileFollowers'
-import {ScreenParams} from '../routes'
 import {useStores} from 'state/index'
 
-export const ProfileFollowers = ({navIdx, visible, params}: ScreenParams) => {
+type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFollowers'>
+export const ProfileFollowersScreen = ({route}: Props) => {
   const store = useStores()
-  const {name} = params
+  const {name} = route.params
 
-  useEffect(() => {
-    if (visible) {
-      store.nav.setTitle(navIdx, `Followers of ${name}`)
+  useFocusEffect(
+    React.useCallback(() => {
       store.shell.setMinimalShellMode(false)
-    }
-  }, [store, visible, name, navIdx])
+    }, [store]),
+  )
 
   return (
     <View>
diff --git a/src/view/screens/ProfileFollows.tsx b/src/view/screens/ProfileFollows.tsx
index 1cdb5bccf..7edf8edba 100644
--- a/src/view/screens/ProfileFollows.tsx
+++ b/src/view/screens/ProfileFollows.tsx
@@ -1,20 +1,21 @@
-import React, {useEffect} from 'react'
+import React from 'react'
 import {View} from 'react-native'
+import {useFocusEffect} from '@react-navigation/native'
+import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
 import {ViewHeader} from '../com/util/ViewHeader'
 import {ProfileFollows as ProfileFollowsComponent} from '../com/profile/ProfileFollows'
-import {ScreenParams} from '../routes'
 import {useStores} from 'state/index'
 
-export const ProfileFollows = ({navIdx, visible, params}: ScreenParams) => {
+type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFollows'>
+export const ProfileFollowsScreen = ({route}: Props) => {
   const store = useStores()
-  const {name} = params
+  const {name} = route.params
 
-  useEffect(() => {
-    if (visible) {
-      store.nav.setTitle(navIdx, `Followed by ${name}`)
+  useFocusEffect(
+    React.useCallback(() => {
       store.shell.setMinimalShellMode(false)
-    }
-  }, [store, visible, name, navIdx])
+    }, [store]),
+  )
 
   return (
     <View>
diff --git a/src/view/screens/Search.tsx b/src/view/screens/Search.tsx
index a87c41e76..a50d5c6a7 100644
--- a/src/view/screens/Search.tsx
+++ b/src/view/screens/Search.tsx
@@ -7,12 +7,19 @@ import {
   TouchableWithoutFeedback,
   View,
 } from 'react-native'
-import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
+import {useFocusEffect} from '@react-navigation/native'
+import {
+  FontAwesomeIcon,
+  FontAwesomeIconStyle,
+} from '@fortawesome/react-native-fontawesome'
 import {ScrollView} from '../com/util/Views'
+import {
+  NativeStackScreenProps,
+  SearchTabNavigatorParams,
+} from 'lib/routes/types'
 import {observer} from 'mobx-react-lite'
 import {UserAvatar} from '../com/util/UserAvatar'
 import {Text} from '../com/util/text/Text'
-import {ScreenParams} from '../routes'
 import {useStores} from 'state/index'
 import {UserAutocompleteViewModel} from 'state/models/user-autocomplete-view'
 import {s} from 'lib/styles'
@@ -21,14 +28,17 @@ import {WhoToFollow} from '../com/discover/WhoToFollow'
 import {SuggestedPosts} from '../com/discover/SuggestedPosts'
 import {ProfileCard} from '../com/profile/ProfileCard'
 import {usePalette} from 'lib/hooks/usePalette'
+import {useTheme} from 'lib/ThemeContext'
 import {useOnMainScroll} from 'lib/hooks/useOnMainScroll'
 import {useAnalytics} from 'lib/analytics'
 
 const MENU_HITSLOP = {left: 10, top: 10, right: 30, bottom: 10}
 const FIVE_MIN = 5 * 60 * 1e3
 
-export const Search = observer(({navIdx, visible, params}: ScreenParams) => {
+type Props = NativeStackScreenProps<SearchTabNavigatorParams, 'Search'>
+export const SearchScreen = observer<Props>(({}: Props) => {
   const pal = usePalette('default')
+  const theme = useTheme()
   const store = useStores()
   const {track} = useAnalytics()
   const scrollElRef = React.useRef<ScrollView>(null)
@@ -41,33 +51,32 @@ export const Search = observer(({navIdx, visible, params}: ScreenParams) => {
     () => new UserAutocompleteViewModel(store),
     [store],
   )
-  const {name} = params
 
   const onSoftReset = () => {
     scrollElRef.current?.scrollTo({x: 0, y: 0})
   }
 
-  React.useEffect(() => {
-    const softResetSub = store.onScreenSoftReset(onSoftReset)
-    const cleanup = () => {
-      softResetSub.remove()
-    }
+  useFocusEffect(
+    React.useCallback(() => {
+      const softResetSub = store.onScreenSoftReset(onSoftReset)
+      const cleanup = () => {
+        softResetSub.remove()
+      }
 
-    if (visible) {
       const now = Date.now()
       if (now - lastRenderTime > FIVE_MIN) {
         setRenderTime(Date.now()) // trigger reload of suggestions
       }
       store.shell.setMinimalShellMode(false)
       autocompleteView.setup()
-      store.nav.setTitle(navIdx, 'Search')
-    }
-    return cleanup
-  }, [store, visible, name, navIdx, autocompleteView, lastRenderTime])
+
+      return cleanup
+    }, [store, autocompleteView, lastRenderTime, setRenderTime]),
+  )
 
   const onPressMenu = () => {
     track('ViewHeader:MenuButtonClicked')
-    store.shell.setMainMenuOpen(true)
+    store.shell.openDrawer()
   }
 
   const onChangeQuery = (text: string) => {
@@ -102,12 +111,7 @@ export const Search = observer(({navIdx, visible, params}: ScreenParams) => {
             onPress={onPressMenu}
             hitSlop={MENU_HITSLOP}
             style={styles.headerMenuBtn}>
-            <UserAvatar
-              size={30}
-              handle={store.me.handle}
-              displayName={store.me.displayName}
-              avatar={store.me.avatar}
-            />
+            <UserAvatar size={30} avatar={store.me.avatar} />
           </TouchableOpacity>
           <View
             style={[
@@ -127,13 +131,18 @@ export const Search = observer(({navIdx, visible, params}: ScreenParams) => {
               returnKeyType="search"
               value={query}
               style={[pal.text, styles.headerSearchInput]}
+              keyboardAppearance={theme.colorScheme}
               onFocus={() => setIsInputFocused(true)}
               onBlur={() => setIsInputFocused(false)}
               onChangeText={onChangeQuery}
             />
             {query ? (
               <TouchableOpacity onPress={onPressClearQuery}>
-                <FontAwesomeIcon icon="xmark" size={16} style={pal.textLight} />
+                <FontAwesomeIcon
+                  icon="xmark"
+                  size={16}
+                  style={pal.textLight as FontAwesomeIconStyle}
+                />
               </TouchableOpacity>
             ) : undefined}
           </View>
diff --git a/src/view/screens/Search.web.tsx b/src/view/screens/Search.web.tsx
index 886d49af7..75b5f01ce 100644
--- a/src/view/screens/Search.web.tsx
+++ b/src/view/screens/Search.web.tsx
@@ -1,8 +1,12 @@
 import React from 'react'
 import {StyleSheet, View} from 'react-native'
+import {useFocusEffect} from '@react-navigation/native'
 import {ScrollView} from '../com/util/Views'
 import {observer} from 'mobx-react-lite'
-import {ScreenParams} from '../routes'
+import {
+  NativeStackScreenProps,
+  SearchTabNavigatorParams,
+} from 'lib/routes/types'
 import {useStores} from 'state/index'
 import {s} from 'lib/styles'
 import {WhoToFollow} from '../com/discover/WhoToFollow'
@@ -12,7 +16,8 @@ import {useOnMainScroll} from 'lib/hooks/useOnMainScroll'
 
 const FIVE_MIN = 5 * 60 * 1e3
 
-export const Search = observer(({navIdx, visible}: ScreenParams) => {
+type Props = NativeStackScreenProps<SearchTabNavigatorParams, 'Search'>
+export const SearchScreen = observer(({}: Props) => {
   const pal = usePalette('default')
   const store = useStores()
   const scrollElRef = React.useRef<ScrollView>(null)
@@ -23,22 +28,21 @@ export const Search = observer(({navIdx, visible}: ScreenParams) => {
     scrollElRef.current?.scrollTo({x: 0, y: 0})
   }
 
-  React.useEffect(() => {
-    const softResetSub = store.onScreenSoftReset(onSoftReset)
-    const cleanup = () => {
-      softResetSub.remove()
-    }
+  useFocusEffect(
+    React.useCallback(() => {
+      const softResetSub = store.onScreenSoftReset(onSoftReset)
 
-    if (visible) {
       const now = Date.now()
       if (now - lastRenderTime > FIVE_MIN) {
         setRenderTime(Date.now()) // trigger reload of suggestions
       }
       store.shell.setMinimalShellMode(false)
-      store.nav.setTitle(navIdx, 'Search')
-    }
-    return cleanup
-  }, [store, visible, navIdx, lastRenderTime])
+
+      return () => {
+        softResetSub.remove()
+      }
+    }, [store, lastRenderTime, setRenderTime]),
+  )
 
   return (
     <ScrollView
diff --git a/src/view/screens/Settings.tsx b/src/view/screens/Settings.tsx
index 47e76a124..2e5d2c001 100644
--- a/src/view/screens/Settings.tsx
+++ b/src/view/screens/Settings.tsx
@@ -1,4 +1,4 @@
-import React, {useEffect} from 'react'
+import React from 'react'
 import {
   ActivityIndicator,
   StyleSheet,
@@ -6,13 +6,18 @@ import {
   View,
 } from 'react-native'
 import {
+  useFocusEffect,
+  useNavigation,
+  StackActions,
+} from '@react-navigation/native'
+import {
   FontAwesomeIcon,
   FontAwesomeIconStyle,
 } from '@fortawesome/react-native-fontawesome'
 import {observer} from 'mobx-react-lite'
+import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
 import * as AppInfo from 'lib/app-info'
 import {useStores} from 'state/index'
-import {ScreenParams} from '../routes'
 import {s, colors} from 'lib/styles'
 import {ScrollView} from '../com/util/Views'
 import {ViewHeader} from '../com/util/ViewHeader'
@@ -25,41 +30,38 @@ import {useTheme} from 'lib/ThemeContext'
 import {usePalette} from 'lib/hooks/usePalette'
 import {AccountData} from 'state/models/session'
 import {useAnalytics} from 'lib/analytics'
+import {NavigationProp} from 'lib/routes/types'
 
-export const Settings = observer(function Settings({
-  navIdx,
-  visible,
-}: ScreenParams) {
+type Props = NativeStackScreenProps<CommonNavigatorParams, 'Settings'>
+export const SettingsScreen = observer(function Settings({}: Props) {
   const theme = useTheme()
   const pal = usePalette('default')
   const store = useStores()
+  const navigation = useNavigation<NavigationProp>()
   const {screen, track} = useAnalytics()
   const [isSwitching, setIsSwitching] = React.useState(false)
 
-  useEffect(() => {
-    screen('Settings')
-  }, [screen])
-
-  useEffect(() => {
-    if (!visible) {
-      return
-    }
-    store.shell.setMinimalShellMode(false)
-    store.nav.setTitle(navIdx, 'Settings')
-  }, [visible, store, navIdx])
+  useFocusEffect(
+    React.useCallback(() => {
+      screen('Settings')
+      store.shell.setMinimalShellMode(false)
+    }, [screen, store]),
+  )
 
   const onPressSwitchAccount = async (acct: AccountData) => {
     track('Settings:SwitchAccountButtonClicked')
     setIsSwitching(true)
     if (await store.session.resumeSession(acct)) {
       setIsSwitching(false)
-      store.nav.tab.fixedTabReset()
+      navigation.navigate('HomeTab')
+      navigation.dispatch(StackActions.popToTop())
       Toast.show(`Signed in as ${acct.displayName || acct.handle}`)
       return
     }
     setIsSwitching(false)
     Toast.show('Sorry! We need you to enter your password.')
-    store.nav.tab.fixedTabReset()
+    navigation.navigate('HomeTab')
+    navigation.dispatch(StackActions.popToTop())
     store.session.clear()
   }
   const onPressAddAccount = () => {
@@ -118,12 +120,7 @@ export const Settings = observer(function Settings({
             noFeedback>
             <View style={[pal.view, styles.linkCard]}>
               <View style={styles.avi}>
-                <UserAvatar
-                  size={40}
-                  displayName={store.me.displayName}
-                  handle={store.me.handle || ''}
-                  avatar={store.me.avatar}
-                />
+                <UserAvatar size={40} avatar={store.me.avatar} />
               </View>
               <View style={[s.flex1]}>
                 <Text type="md-bold" style={pal.text} numberOfLines={1}>
@@ -152,12 +149,7 @@ export const Settings = observer(function Settings({
               isSwitching ? undefined : () => onPressSwitchAccount(account)
             }>
             <View style={styles.avi}>
-              <UserAvatar
-                size={40}
-                displayName={account.displayName}
-                handle={account.handle || ''}
-                avatar={account.aviUrl}
-              />
+              <UserAvatar size={40} avatar={account.aviUrl} />
             </View>
             <View style={[s.flex1]}>
               <Text type="md-bold" style={pal.text}>
diff --git a/src/view/shell/mobile/BottomBar.tsx b/src/view/shell/BottomBar.tsx
index 73c2501ab..18b06968f 100644
--- a/src/view/shell/mobile/BottomBar.tsx
+++ b/src/view/shell/BottomBar.tsx
@@ -6,13 +6,14 @@ import {
   TouchableOpacity,
   View,
 } from 'react-native'
+import {StackActions, useNavigationState} from '@react-navigation/native'
+import {BottomTabBarProps} from '@react-navigation/bottom-tabs'
 import {useSafeAreaInsets} from 'react-native-safe-area-context'
 import {observer} from 'mobx-react-lite'
 import {Text} from 'view/com/util/text/Text'
 import {useStores} from 'state/index'
 import {useAnalytics} from 'lib/analytics'
 import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
-import {TabPurpose, TabPurposeMainPath} from 'state/models/navigation'
 import {clamp} from 'lib/numbers'
 import {
   HomeIcon,
@@ -25,13 +26,24 @@ import {
 } from 'lib/icons'
 import {colors} from 'lib/styles'
 import {usePalette} from 'lib/hooks/usePalette'
+import {getTabState, TabState} from 'lib/routes/helpers'
 
-export const BottomBar = observer(() => {
+export const BottomBar = observer(({navigation}: BottomTabBarProps) => {
   const store = useStores()
   const pal = usePalette('default')
   const minimalShellInterp = useAnimatedValue(0)
   const safeAreaInsets = useSafeAreaInsets()
   const {track} = useAnalytics()
+  const {isAtHome, isAtSearch, isAtNotifications} = useNavigationState(
+    state => {
+      return {
+        isAtHome: getTabState(state, 'Home') !== TabState.Outside,
+        isAtSearch: getTabState(state, 'Search') !== TabState.Outside,
+        isAtNotifications:
+          getTabState(state, 'Notifications') !== TabState.Outside,
+      }
+    },
+  )
 
   React.useEffect(() => {
     if (store.shell.minimalShellMode) {
@@ -54,62 +66,34 @@ export const BottomBar = observer(() => {
     transform: [{translateY: Animated.multiply(minimalShellInterp, 100)}],
   }
 
-  const onPressHome = React.useCallback(() => {
-    track('MobileShell:HomeButtonPressed')
-    if (store.nav.tab.fixedTabPurpose === TabPurpose.Default) {
-      if (!store.nav.tab.canGoBack) {
+  const onPressTab = React.useCallback(
+    (tab: string) => {
+      track(`MobileShell:${tab}ButtonPressed`)
+      const state = navigation.getState()
+      const tabState = getTabState(state, tab)
+      if (tabState === TabState.InsideAtRoot) {
         store.emitScreenSoftReset()
+      } else if (tabState === TabState.Inside) {
+        navigation.dispatch(StackActions.popToTop())
       } else {
-        store.nav.tab.fixedTabReset()
-      }
-    } else {
-      store.nav.switchTo(TabPurpose.Default, false)
-      if (store.nav.tab.index === 0) {
-        store.nav.tab.fixedTabReset()
+        navigation.navigate(`${tab}Tab`)
       }
-    }
-  }, [store, track])
-  const onPressSearch = React.useCallback(() => {
-    track('MobileShell:SearchButtonPressed')
-    if (store.nav.tab.fixedTabPurpose === TabPurpose.Search) {
-      if (!store.nav.tab.canGoBack) {
-        store.emitScreenSoftReset()
-      } else {
-        store.nav.tab.fixedTabReset()
-      }
-    } else {
-      store.nav.switchTo(TabPurpose.Search, false)
-      if (store.nav.tab.index === 0) {
-        store.nav.tab.fixedTabReset()
-      }
-    }
-  }, [store, track])
-  const onPressNotifications = React.useCallback(() => {
-    track('MobileShell:NotificationsButtonPressed')
-    if (store.nav.tab.fixedTabPurpose === TabPurpose.Notifs) {
-      if (!store.nav.tab.canGoBack) {
-        store.emitScreenSoftReset()
-      } else {
-        store.nav.tab.fixedTabReset()
-      }
-    } else {
-      store.nav.switchTo(TabPurpose.Notifs, false)
-      if (store.nav.tab.index === 0) {
-        store.nav.tab.fixedTabReset()
-      }
-    }
-  }, [store, track])
+    },
+    [store, track, navigation],
+  )
+  const onPressHome = React.useCallback(() => onPressTab('Home'), [onPressTab])
+  const onPressSearch = React.useCallback(
+    () => onPressTab('Search'),
+    [onPressTab],
+  )
+  const onPressNotifications = React.useCallback(
+    () => onPressTab('Notifications'),
+    [onPressTab],
+  )
   const onPressProfile = React.useCallback(() => {
     track('MobileShell:ProfileButtonPressed')
-    store.nav.navigate(`/profile/${store.me.handle}`)
-  }, [store, track])
-
-  const isAtHome =
-    store.nav.tab.current.url === TabPurposeMainPath[TabPurpose.Default]
-  const isAtSearch =
-    store.nav.tab.current.url === TabPurposeMainPath[TabPurpose.Search]
-  const isAtNotifications =
-    store.nav.tab.current.url === TabPurposeMainPath[TabPurpose.Notifs]
+    navigation.navigate('Profile', {name: store.me.handle})
+  }, [navigation, track, store.me.handle])
 
   return (
     <Animated.View
diff --git a/src/view/shell/mobile/Composer.tsx b/src/view/shell/Composer.tsx
index 5fca118bd..2ab01c656 100644
--- a/src/view/shell/mobile/Composer.tsx
+++ b/src/view/shell/Composer.tsx
@@ -1,7 +1,7 @@
 import React, {useEffect} from 'react'
 import {observer} from 'mobx-react-lite'
 import {Animated, Easing, Platform, StyleSheet, View} from 'react-native'
-import {ComposePost} from '../../com/composer/ComposePost'
+import {ComposePost} from '../com/composer/Composer'
 import {ComposerOpts} from 'state/models/shell-ui'
 import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
 import {usePalette} from 'lib/hooks/usePalette'
@@ -11,7 +11,6 @@ export const Composer = observer(
     active,
     winHeight,
     replyTo,
-    imagesOpen,
     onPost,
     onClose,
     quote,
@@ -19,7 +18,6 @@ export const Composer = observer(
     active: boolean
     winHeight: number
     replyTo?: ComposerOpts['replyTo']
-    imagesOpen?: ComposerOpts['imagesOpen']
     onPost?: ComposerOpts['onPost']
     onClose: () => void
     quote?: ComposerOpts['quote']
@@ -61,7 +59,6 @@ export const Composer = observer(
       <Animated.View style={[styles.wrapper, pal.view, wrapperAnimStyle]}>
         <ComposePost
           replyTo={replyTo}
-          imagesOpen={imagesOpen}
           onPost={onPost}
           onClose={onClose}
           quote={quote}
diff --git a/src/view/shell/web/Composer.tsx b/src/view/shell/Composer.web.tsx
index 0d8484262..465b475fb 100644
--- a/src/view/shell/web/Composer.tsx
+++ b/src/view/shell/Composer.web.tsx
@@ -1,7 +1,7 @@
 import React from 'react'
 import {observer} from 'mobx-react-lite'
 import {StyleSheet, View} from 'react-native'
-import {ComposePost} from '../../com/composer/ComposePost'
+import {ComposePost} from '../com/composer/Composer'
 import {ComposerOpts} from 'state/models/shell-ui'
 import {usePalette} from 'lib/hooks/usePalette'
 
@@ -9,14 +9,12 @@ export const Composer = observer(
   ({
     active,
     replyTo,
-    imagesOpen,
     onPost,
     onClose,
   }: {
     active: boolean
     winHeight: number
     replyTo?: ComposerOpts['replyTo']
-    imagesOpen?: ComposerOpts['imagesOpen']
     onPost?: ComposerOpts['onPost']
     onClose: () => void
   }) => {
@@ -32,12 +30,7 @@ export const Composer = observer(
     return (
       <View style={styles.mask}>
         <View style={[styles.container, pal.view]}>
-          <ComposePost
-            replyTo={replyTo}
-            imagesOpen={imagesOpen}
-            onPost={onPost}
-            onClose={onClose}
-          />
+          <ComposePost replyTo={replyTo} onPost={onPost} onClose={onClose} />
         </View>
       </View>
     )
diff --git a/src/view/shell/Drawer.tsx b/src/view/shell/Drawer.tsx
new file mode 100644
index 000000000..80944e10a
--- /dev/null
+++ b/src/view/shell/Drawer.tsx
@@ -0,0 +1,386 @@
+import React from 'react'
+import {
+  Linking,
+  SafeAreaView,
+  StyleProp,
+  StyleSheet,
+  TouchableOpacity,
+  View,
+  ViewStyle,
+} from 'react-native'
+import {
+  useNavigation,
+  useNavigationState,
+  StackActions,
+} from '@react-navigation/native'
+import {observer} from 'mobx-react-lite'
+import {
+  FontAwesomeIcon,
+  FontAwesomeIconStyle,
+} from '@fortawesome/react-native-fontawesome'
+import {s, colors} from 'lib/styles'
+import {FEEDBACK_FORM_URL} from 'lib/constants'
+import {useStores} from 'state/index'
+import {
+  HomeIcon,
+  HomeIconSolid,
+  BellIcon,
+  BellIconSolid,
+  UserIcon,
+  CogIcon,
+  MagnifyingGlassIcon2,
+  MagnifyingGlassIcon2Solid,
+  MoonIcon,
+} from 'lib/icons'
+import {UserAvatar} from 'view/com/util/UserAvatar'
+import {Text} from 'view/com/util/text/Text'
+import {useTheme} from 'lib/ThemeContext'
+import {usePalette} from 'lib/hooks/usePalette'
+import {useAnalytics} from 'lib/analytics'
+import {pluralize} from 'lib/strings/helpers'
+import {getCurrentRoute, isTab, getTabState, TabState} from 'lib/routes/helpers'
+import {NavigationProp} from 'lib/routes/types'
+
+export const DrawerContent = observer(() => {
+  const theme = useTheme()
+  const pal = usePalette('default')
+  const store = useStores()
+  const navigation = useNavigation<NavigationProp>()
+  const {track} = useAnalytics()
+  const {isAtHome, isAtSearch, isAtNotifications} = useNavigationState(
+    state => {
+      const currentRoute = state ? getCurrentRoute(state) : false
+      return {
+        isAtHome: currentRoute ? isTab(currentRoute.name, 'Home') : true,
+        isAtSearch: currentRoute ? isTab(currentRoute.name, 'Search') : false,
+        isAtNotifications: currentRoute
+          ? isTab(currentRoute.name, 'Notifications')
+          : false,
+      }
+    },
+  )
+
+  // events
+  // =
+
+  const onPressTab = React.useCallback(
+    (tab: string) => {
+      track('Menu:ItemClicked', {url: tab})
+      const state = navigation.getState()
+      store.shell.closeDrawer()
+      const tabState = getTabState(state, tab)
+      if (tabState === TabState.InsideAtRoot) {
+        store.emitScreenSoftReset()
+      } else if (tabState === TabState.Inside) {
+        navigation.dispatch(StackActions.popToTop())
+      } else {
+        // @ts-ignore must be Home, Search, or Notifications
+        navigation.navigate(`${tab}Tab`)
+      }
+    },
+    [store, track, navigation],
+  )
+
+  const onPressHome = React.useCallback(() => onPressTab('Home'), [onPressTab])
+
+  const onPressSearch = React.useCallback(
+    () => onPressTab('Search'),
+    [onPressTab],
+  )
+
+  const onPressNotifications = React.useCallback(
+    () => onPressTab('Notifications'),
+    [onPressTab],
+  )
+
+  const onPressProfile = React.useCallback(() => {
+    track('Menu:ItemClicked', {url: 'Profile'})
+    navigation.navigate('Profile', {name: store.me.handle})
+    store.shell.closeDrawer()
+  }, [navigation, track, store.me.handle, store.shell])
+
+  const onPressSettings = React.useCallback(() => {
+    track('Menu:ItemClicked', {url: 'Settings'})
+    navigation.navigate('Settings')
+    store.shell.closeDrawer()
+  }, [navigation, track, store.shell])
+
+  const onPressFeedback = () => {
+    track('Menu:FeedbackClicked')
+    Linking.openURL(FEEDBACK_FORM_URL)
+  }
+
+  // rendering
+  // =
+
+  const MenuItem = ({
+    icon,
+    label,
+    count,
+    bold,
+    onPress,
+  }: {
+    icon: JSX.Element
+    label: string
+    count?: number
+    bold?: boolean
+    onPress: () => void
+  }) => (
+    <TouchableOpacity
+      testID={`menuItemButton-${label}`}
+      style={styles.menuItem}
+      onPress={onPress}>
+      <View style={[styles.menuItemIconWrapper]}>
+        {icon}
+        {count ? (
+          <View style={styles.menuItemCount}>
+            <Text style={styles.menuItemCountLabel}>{count}</Text>
+          </View>
+        ) : undefined}
+      </View>
+      <Text
+        type={bold ? '2xl-bold' : '2xl'}
+        style={[pal.text, s.flex1]}
+        numberOfLines={1}>
+        {label}
+      </Text>
+    </TouchableOpacity>
+  )
+
+  const onDarkmodePress = () => {
+    track('Menu:ItemClicked', {url: '/darkmode'})
+    store.shell.setDarkMode(!store.shell.darkMode)
+  }
+
+  return (
+    <View
+      testID="menuView"
+      style={[
+        styles.view,
+        theme.colorScheme === 'light' ? pal.view : styles.viewDarkMode,
+      ]}>
+      <SafeAreaView style={s.flex1}>
+        <TouchableOpacity testID="profileCardButton" onPress={onPressProfile}>
+          <UserAvatar size={80} avatar={store.me.avatar} />
+          <Text
+            type="title-lg"
+            style={[pal.text, s.bold, styles.profileCardDisplayName]}>
+            {store.me.displayName || store.me.handle}
+          </Text>
+          <Text type="2xl" style={[pal.textLight, styles.profileCardHandle]}>
+            @{store.me.handle}
+          </Text>
+          <Text type="xl" style={[pal.textLight, styles.profileCardFollowers]}>
+            <Text type="xl-medium" style={pal.text}>
+              {store.me.followersCount || 0}
+            </Text>{' '}
+            {pluralize(store.me.followersCount || 0, 'follower')} &middot;{' '}
+            <Text type="xl-medium" style={pal.text}>
+              {store.me.followsCount || 0}
+            </Text>{' '}
+            following
+          </Text>
+        </TouchableOpacity>
+        <View style={s.flex1} />
+        <View>
+          <MenuItem
+            icon={
+              isAtSearch ? (
+                <MagnifyingGlassIcon2Solid
+                  style={pal.text as StyleProp<ViewStyle>}
+                  size={24}
+                  strokeWidth={1.7}
+                />
+              ) : (
+                <MagnifyingGlassIcon2
+                  style={pal.text as StyleProp<ViewStyle>}
+                  size={24}
+                  strokeWidth={1.7}
+                />
+              )
+            }
+            label="Search"
+            bold={isAtSearch}
+            onPress={onPressSearch}
+          />
+          <MenuItem
+            icon={
+              isAtHome ? (
+                <HomeIconSolid
+                  style={pal.text as StyleProp<ViewStyle>}
+                  size="24"
+                  strokeWidth={3.25}
+                />
+              ) : (
+                <HomeIcon
+                  style={pal.text as StyleProp<ViewStyle>}
+                  size="24"
+                  strokeWidth={3.25}
+                />
+              )
+            }
+            label="Home"
+            bold={isAtHome}
+            onPress={onPressHome}
+          />
+          <MenuItem
+            icon={
+              isAtNotifications ? (
+                <BellIconSolid
+                  style={pal.text as StyleProp<ViewStyle>}
+                  size="24"
+                  strokeWidth={1.7}
+                />
+              ) : (
+                <BellIcon
+                  style={pal.text as StyleProp<ViewStyle>}
+                  size="24"
+                  strokeWidth={1.7}
+                />
+              )
+            }
+            label="Notifications"
+            count={store.me.notifications.unreadCount}
+            bold={isAtNotifications}
+            onPress={onPressNotifications}
+          />
+          <MenuItem
+            icon={
+              <UserIcon
+                style={pal.text as StyleProp<ViewStyle>}
+                size="26"
+                strokeWidth={1.5}
+              />
+            }
+            label="Profile"
+            onPress={onPressProfile}
+          />
+          <MenuItem
+            icon={
+              <CogIcon
+                style={pal.text as StyleProp<ViewStyle>}
+                size="26"
+                strokeWidth={1.75}
+              />
+            }
+            label="Settings"
+            onPress={onPressSettings}
+          />
+        </View>
+        <View style={s.flex1} />
+        <View style={styles.footer}>
+          <TouchableOpacity
+            onPress={onDarkmodePress}
+            style={[
+              styles.footerBtn,
+              theme.colorScheme === 'light'
+                ? pal.btn
+                : styles.footerBtnDarkMode,
+            ]}>
+            <MoonIcon
+              size={22}
+              style={pal.text as StyleProp<ViewStyle>}
+              strokeWidth={2}
+            />
+          </TouchableOpacity>
+          <TouchableOpacity
+            onPress={onPressFeedback}
+            style={[
+              styles.footerBtn,
+              styles.footerBtnFeedback,
+              theme.colorScheme === 'light'
+                ? styles.footerBtnFeedbackLight
+                : styles.footerBtnFeedbackDark,
+            ]}>
+            <FontAwesomeIcon
+              style={pal.link as FontAwesomeIconStyle}
+              size={19}
+              icon={['far', 'message']}
+            />
+            <Text type="2xl-medium" style={[pal.link, s.pl10]}>
+              Feedback
+            </Text>
+          </TouchableOpacity>
+        </View>
+      </SafeAreaView>
+    </View>
+  )
+})
+
+const styles = StyleSheet.create({
+  view: {
+    flex: 1,
+    paddingTop: 20,
+    paddingBottom: 50,
+    paddingLeft: 20,
+  },
+  viewDarkMode: {
+    backgroundColor: '#1B1919',
+  },
+
+  profileCardDisplayName: {
+    marginTop: 20,
+    paddingRight: 30,
+  },
+  profileCardHandle: {
+    marginTop: 4,
+    paddingRight: 30,
+  },
+  profileCardFollowers: {
+    marginTop: 16,
+    paddingRight: 30,
+  },
+
+  menuItem: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    paddingVertical: 16,
+    paddingRight: 10,
+  },
+  menuItemIconWrapper: {
+    width: 24,
+    height: 24,
+    alignItems: 'center',
+    justifyContent: 'center',
+    marginRight: 12,
+  },
+  menuItemCount: {
+    position: 'absolute',
+    right: -6,
+    top: -2,
+    backgroundColor: colors.red3,
+    paddingHorizontal: 4,
+    paddingBottom: 1,
+    borderRadius: 6,
+  },
+  menuItemCountLabel: {
+    fontSize: 12,
+    fontWeight: 'bold',
+    color: colors.white,
+  },
+
+  footer: {
+    flexDirection: 'row',
+    justifyContent: 'space-between',
+    paddingRight: 30,
+    paddingTop: 80,
+  },
+  footerBtn: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    padding: 10,
+    borderRadius: 25,
+  },
+  footerBtnDarkMode: {
+    backgroundColor: colors.black,
+  },
+  footerBtnFeedback: {
+    paddingHorizontal: 24,
+  },
+  footerBtnFeedbackLight: {
+    backgroundColor: '#DDEFFF',
+  },
+  footerBtnFeedbackDark: {
+    backgroundColor: colors.blue6,
+  },
+})
diff --git a/src/view/shell/desktop/LeftNav.tsx b/src/view/shell/desktop/LeftNav.tsx
new file mode 100644
index 000000000..46c77178b
--- /dev/null
+++ b/src/view/shell/desktop/LeftNav.tsx
@@ -0,0 +1,254 @@
+import React from 'react'
+import {observer} from 'mobx-react-lite'
+import {StyleSheet, TouchableOpacity, View} from 'react-native'
+import {useNavigation, useNavigationState} from '@react-navigation/native'
+import {
+  FontAwesomeIcon,
+  FontAwesomeIconStyle,
+} from '@fortawesome/react-native-fontawesome'
+import {Text} from 'view/com/util/text/Text'
+import {UserAvatar} from 'view/com/util/UserAvatar'
+import {Link} from 'view/com/util/Link'
+import {usePalette} from 'lib/hooks/usePalette'
+import {useStores} from 'state/index'
+import {s, colors} from 'lib/styles'
+import {
+  HomeIcon,
+  HomeIconSolid,
+  MagnifyingGlassIcon2,
+  MagnifyingGlassIcon2Solid,
+  BellIcon,
+  BellIconSolid,
+  UserIcon,
+  UserIconSolid,
+  CogIcon,
+  CogIconSolid,
+  ComposeIcon2,
+} from 'lib/icons'
+import {getCurrentRoute, isTab, isStateAtTabRoot} from 'lib/routes/helpers'
+import {NavigationProp} from 'lib/routes/types'
+import {router} from '../../../routes'
+
+const ProfileCard = observer(() => {
+  const store = useStores()
+  return (
+    <Link href={`/profile/${store.me.handle}`} style={styles.profileCard}>
+      <UserAvatar avatar={store.me.avatar} size={64} />
+    </Link>
+  )
+})
+
+function BackBtn() {
+  const pal = usePalette('default')
+  const navigation = useNavigation<NavigationProp>()
+  const shouldShow = useNavigationState(state => !isStateAtTabRoot(state))
+
+  const onPressBack = React.useCallback(() => {
+    if (navigation.canGoBack()) {
+      navigation.goBack()
+    } else {
+      navigation.navigate('Home')
+    }
+  }, [navigation])
+
+  if (!shouldShow) {
+    return <></>
+  }
+  return (
+    <TouchableOpacity
+      testID="viewHeaderBackOrMenuBtn"
+      onPress={onPressBack}
+      style={styles.backBtn}>
+      <FontAwesomeIcon
+        size={24}
+        icon="angle-left"
+        style={pal.text as FontAwesomeIconStyle}
+      />
+    </TouchableOpacity>
+  )
+}
+
+interface NavItemProps {
+  count?: number
+  href: string
+  icon: JSX.Element
+  iconFilled: JSX.Element
+  label: string
+}
+const NavItem = observer(
+  ({count, href, icon, iconFilled, label}: NavItemProps) => {
+    const pal = usePalette('default')
+    const [pathName] = React.useMemo(() => router.matchPath(href), [href])
+    const currentRouteName = useNavigationState(state => {
+      if (!state) {
+        return 'Home'
+      }
+      return getCurrentRoute(state).name
+    })
+    const isCurrent = isTab(currentRouteName, pathName)
+
+    return (
+      <Link href={href} style={styles.navItem}>
+        <View style={[styles.navItemIconWrapper]}>
+          {isCurrent ? iconFilled : icon}
+          {typeof count === 'number' && count > 0 && (
+            <Text type="button" style={styles.navItemCount}>
+              {count}
+            </Text>
+          )}
+        </View>
+        <Text type="title" style={[isCurrent ? s.bold : s.normal, pal.text]}>
+          {label}
+        </Text>
+      </Link>
+    )
+  },
+)
+
+function ComposeBtn() {
+  const store = useStores()
+  const onPressCompose = () => store.shell.openComposer({})
+
+  return (
+    <TouchableOpacity style={[styles.newPostBtn]} onPress={onPressCompose}>
+      <View style={styles.newPostBtnIconWrapper}>
+        <ComposeIcon2
+          size={19}
+          strokeWidth={2}
+          style={styles.newPostBtnLabel}
+        />
+      </View>
+      <Text type="button" style={styles.newPostBtnLabel}>
+        New Post
+      </Text>
+    </TouchableOpacity>
+  )
+}
+
+export const DesktopLeftNav = observer(function DesktopLeftNav() {
+  const store = useStores()
+  const pal = usePalette('default')
+
+  return (
+    <View style={styles.leftNav}>
+      <ProfileCard />
+      <BackBtn />
+      <NavItem
+        href="/"
+        icon={<HomeIcon size={24} style={pal.text} />}
+        iconFilled={
+          <HomeIconSolid strokeWidth={4} size={24} style={pal.text} />
+        }
+        label="Home"
+      />
+      <NavItem
+        href="/search"
+        icon={
+          <MagnifyingGlassIcon2 strokeWidth={2} size={24} style={pal.text} />
+        }
+        iconFilled={
+          <MagnifyingGlassIcon2Solid
+            strokeWidth={2}
+            size={24}
+            style={pal.text}
+          />
+        }
+        label="Search"
+      />
+      <NavItem
+        href="/notifications"
+        count={store.me.notifications.unreadCount}
+        icon={<BellIcon strokeWidth={2} size={24} style={pal.text} />}
+        iconFilled={
+          <BellIconSolid strokeWidth={1.5} size={24} style={pal.text} />
+        }
+        label="Notifications"
+      />
+      <NavItem
+        href={`/profile/${store.me.handle}`}
+        icon={<UserIcon strokeWidth={1.75} size={28} style={pal.text} />}
+        iconFilled={
+          <UserIconSolid strokeWidth={1.75} size={28} style={pal.text} />
+        }
+        label="Profile"
+      />
+      <NavItem
+        href="/settings"
+        icon={<CogIcon strokeWidth={1.75} size={28} style={pal.text} />}
+        iconFilled={
+          <CogIconSolid strokeWidth={1.5} size={28} style={pal.text} />
+        }
+        label="Settings"
+      />
+      <ComposeBtn />
+    </View>
+  )
+})
+
+const styles = StyleSheet.create({
+  leftNav: {
+    position: 'absolute',
+    top: 10,
+    right: 'calc(50vw + 300px)',
+    width: 220,
+  },
+
+  profileCard: {
+    marginVertical: 10,
+    width: 60,
+  },
+
+  backBtn: {
+    position: 'absolute',
+    top: 12,
+    right: 12,
+    width: 30,
+    height: 30,
+  },
+
+  navItem: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    paddingTop: 14,
+    paddingBottom: 10,
+  },
+  navItemIconWrapper: {
+    alignItems: 'center',
+    justifyContent: 'center',
+    width: 28,
+    height: 28,
+    marginRight: 10,
+    marginTop: 2,
+  },
+  navItemCount: {
+    position: 'absolute',
+    top: 0,
+    left: 15,
+    backgroundColor: colors.blue3,
+    color: colors.white,
+    fontSize: 12,
+    fontWeight: 'bold',
+    paddingHorizontal: 4,
+    borderRadius: 6,
+  },
+
+  newPostBtn: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    justifyContent: 'center',
+    width: 136,
+    borderRadius: 24,
+    paddingVertical: 10,
+    paddingHorizontal: 16,
+    backgroundColor: colors.blue3,
+    marginTop: 20,
+  },
+  newPostBtnIconWrapper: {
+    marginRight: 8,
+  },
+  newPostBtnLabel: {
+    color: colors.white,
+    fontSize: 16,
+    fontWeight: 'bold',
+  },
+})
diff --git a/src/view/shell/desktop/RightNav.tsx b/src/view/shell/desktop/RightNav.tsx
new file mode 100644
index 000000000..a196951af
--- /dev/null
+++ b/src/view/shell/desktop/RightNav.tsx
@@ -0,0 +1,46 @@
+import React from 'react'
+import {observer} from 'mobx-react-lite'
+import {StyleSheet, View} from 'react-native'
+import {usePalette} from 'lib/hooks/usePalette'
+import {DesktopSearch} from './Search'
+import {Text} from 'view/com/util/text/Text'
+import {TextLink} from 'view/com/util/Link'
+import {FEEDBACK_FORM_URL} from 'lib/constants'
+
+export const DesktopRightNav = observer(function DesktopRightNav() {
+  const pal = usePalette('default')
+  return (
+    <View style={[styles.rightNav, pal.view]}>
+      <DesktopSearch />
+      <View style={styles.message}>
+        <Text type="md" style={[pal.textLight, styles.messageLine]}>
+          Welcome to Bluesky! This is a beta application that's still in
+          development.
+        </Text>
+        <TextLink
+          type="md"
+          style={pal.link}
+          href={FEEDBACK_FORM_URL}
+          text="Send feedback"
+        />
+      </View>
+    </View>
+  )
+})
+
+const styles = StyleSheet.create({
+  rightNav: {
+    position: 'absolute',
+    top: 20,
+    left: 'calc(50vw + 330px)',
+    width: 300,
+  },
+
+  message: {
+    marginTop: 20,
+    paddingHorizontal: 10,
+  },
+  messageLine: {
+    marginBottom: 10,
+  },
+})
diff --git a/src/view/shell/web/DesktopSearch.tsx b/src/view/shell/desktop/Search.tsx
index 43f13ca2b..7c96dbac2 100644
--- a/src/view/shell/web/DesktopSearch.tsx
+++ b/src/view/shell/desktop/Search.tsx
@@ -1,11 +1,12 @@
 import React from 'react'
-import {TextInput, View, StyleSheet, TouchableOpacity, Text} from 'react-native'
+import {TextInput, View, StyleSheet, TouchableOpacity} from 'react-native'
 import {UserAutocompleteViewModel} from 'state/models/user-autocomplete-view'
 import {observer} from 'mobx-react-lite'
 import {useStores} from 'state/index'
 import {usePalette} from 'lib/hooks/usePalette'
-import {MagnifyingGlassIcon} from 'lib/icons'
-import {ProfileCard} from '../../com/profile/ProfileCard'
+import {MagnifyingGlassIcon2} from 'lib/icons'
+import {ProfileCard} from 'view/com/profile/ProfileCard'
+import {Text} from 'view/com/util/text/Text'
 
 export const DesktopSearch = observer(function DesktopSearch() {
   const store = useStores()
@@ -35,9 +36,10 @@ export const DesktopSearch = observer(function DesktopSearch() {
 
   return (
     <View style={styles.container}>
-      <View style={[pal.borderDark, pal.view, styles.search]}>
+      <View
+        style={[{backgroundColor: pal.colors.backgroundLight}, styles.search]}>
         <View style={[styles.inputContainer]}>
-          <MagnifyingGlassIcon
+          <MagnifyingGlassIcon2
             size={18}
             style={[pal.textLight, styles.iconWrapper]}
           />
@@ -57,7 +59,9 @@ export const DesktopSearch = observer(function DesktopSearch() {
           {query ? (
             <View style={styles.cancelBtn}>
               <TouchableOpacity onPress={onPressCancelSearch}>
-                <Text style={[pal.link]}>Cancel</Text>
+                <Text type="lg" style={[pal.link]}>
+                  Cancel
+                </Text>
               </TouchableOpacity>
             </View>
           ) : undefined}
@@ -97,21 +101,23 @@ const styles = StyleSheet.create({
     width: 300,
   },
   search: {
-    paddingHorizontal: 10,
+    paddingHorizontal: 16,
+    paddingVertical: 2,
     width: 300,
     borderRadius: 20,
-    borderWidth: 1,
   },
   inputContainer: {
     flexDirection: 'row',
   },
   iconWrapper: {
+    position: 'relative',
+    top: 2,
     paddingVertical: 7,
-    marginRight: 4,
+    marginRight: 8,
   },
   input: {
     flex: 1,
-    fontSize: 16,
+    fontSize: 18,
     width: '100%',
     paddingTop: 7,
     paddingBottom: 7,
diff --git a/src/view/shell/index.tsx b/src/view/shell/index.tsx
new file mode 100644
index 000000000..116915ff4
--- /dev/null
+++ b/src/view/shell/index.tsx
@@ -0,0 +1,139 @@
+import React from 'react'
+import {observer} from 'mobx-react-lite'
+import {StatusBar, StyleSheet, useWindowDimensions, View} from 'react-native'
+import {useSafeAreaInsets} from 'react-native-safe-area-context'
+import {Drawer} from 'react-native-drawer-layout'
+import {useNavigationState} from '@react-navigation/native'
+import {useStores} from 'state/index'
+import {Login} from 'view/screens/Login'
+import {ModalsContainer} from 'view/com/modals/Modal'
+import {Lightbox} from 'view/com/lightbox/Lightbox'
+import {Text} from 'view/com/util/text/Text'
+import {ErrorBoundary} from 'view/com/util/ErrorBoundary'
+import {DrawerContent} from './Drawer'
+import {Composer} from './Composer'
+import {s} from 'lib/styles'
+import {useTheme} from 'lib/ThemeContext'
+import {usePalette} from 'lib/hooks/usePalette'
+import {RoutesContainer, TabsNavigator} from '../../Navigation'
+import {isStateAtTabRoot} from 'lib/routes/helpers'
+
+const ShellInner = observer(() => {
+  const store = useStores()
+  const winDim = useWindowDimensions()
+  const safeAreaInsets = useSafeAreaInsets()
+  const containerPadding = React.useMemo(
+    () => ({height: '100%', paddingTop: safeAreaInsets.top}),
+    [safeAreaInsets],
+  )
+  const renderDrawerContent = React.useCallback(() => <DrawerContent />, [])
+  const onOpenDrawer = React.useCallback(
+    () => store.shell.openDrawer(),
+    [store],
+  )
+  const onCloseDrawer = React.useCallback(
+    () => store.shell.closeDrawer(),
+    [store],
+  )
+  const canGoBack = useNavigationState(state => !isStateAtTabRoot(state))
+
+  return (
+    <>
+      <View style={containerPadding}>
+        <ErrorBoundary>
+          <Drawer
+            renderDrawerContent={renderDrawerContent}
+            open={store.shell.isDrawerOpen}
+            onOpen={onOpenDrawer}
+            onClose={onCloseDrawer}
+            swipeEdgeWidth={winDim.width}
+            swipeEnabled={!canGoBack}>
+            <TabsNavigator />
+          </Drawer>
+        </ErrorBoundary>
+      </View>
+      <ModalsContainer />
+      <Lightbox />
+      <Composer
+        active={store.shell.isComposerActive}
+        onClose={() => store.shell.closeComposer()}
+        winHeight={winDim.height}
+        replyTo={store.shell.composerOpts?.replyTo}
+        onPost={store.shell.composerOpts?.onPost}
+        quote={store.shell.composerOpts?.quote}
+      />
+    </>
+  )
+})
+
+export const Shell: React.FC = observer(() => {
+  const theme = useTheme()
+  const pal = usePalette('default')
+  const store = useStores()
+
+  if (store.hackUpgradeNeeded) {
+    return (
+      <View style={styles.outerContainer}>
+        <View style={[s.flexCol, s.p20, s.h100pct]}>
+          <View style={s.flex1} />
+          <View>
+            <Text type="title-2xl" style={s.pb10}>
+              Update required
+            </Text>
+            <Text style={[s.pb20, s.bold]}>
+              Please update your app to the latest version. If no update is
+              available yet, please check the App Store in a day or so.
+            </Text>
+            <Text type="title" style={s.pb10}>
+              What's happening?
+            </Text>
+            <Text style={s.pb10}>
+              We're in the final stages of the AT Protocol's v1 development. To
+              make sure everything works as well as possible, we're making final
+              breaking changes to the APIs.
+            </Text>
+            <Text>
+              If we didn't botch this process, a new version of the app should
+              be available now.
+            </Text>
+          </View>
+          <View style={s.flex1} />
+          <View style={s.footerSpacer} />
+        </View>
+      </View>
+    )
+  }
+
+  if (!store.session.hasSession) {
+    return (
+      <View style={styles.outerContainer}>
+        <StatusBar
+          barStyle={
+            theme.colorScheme === 'dark' ? 'light-content' : 'dark-content'
+          }
+        />
+        <Login />
+        <ModalsContainer />
+      </View>
+    )
+  }
+
+  return (
+    <View testID="mobileShellView" style={[styles.outerContainer, pal.view]}>
+      <StatusBar
+        barStyle={
+          theme.colorScheme === 'dark' ? 'light-content' : 'dark-content'
+        }
+      />
+      <RoutesContainer>
+        <ShellInner />
+      </RoutesContainer>
+    </View>
+  )
+})
+
+const styles = StyleSheet.create({
+  outerContainer: {
+    height: '100%',
+  },
+})
diff --git a/src/view/shell/index.web.tsx b/src/view/shell/index.web.tsx
new file mode 100644
index 000000000..9a97505e8
--- /dev/null
+++ b/src/view/shell/index.web.tsx
@@ -0,0 +1,113 @@
+import React from 'react'
+import {observer} from 'mobx-react-lite'
+import {View, StyleSheet} from 'react-native'
+import {useStores} from 'state/index'
+import {DesktopLeftNav} from './desktop/LeftNav'
+import {DesktopRightNav} from './desktop/RightNav'
+import {Login} from '../screens/Login'
+import {ErrorBoundary} from '../com/util/ErrorBoundary'
+import {Lightbox} from '../com/lightbox/Lightbox'
+import {ModalsContainer} from '../com/modals/Modal'
+import {Text} from 'view/com/util/text/Text'
+import {Composer} from './Composer.web'
+import {usePalette} from 'lib/hooks/usePalette'
+import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle'
+import {s, colors} from 'lib/styles'
+import {isMobileWeb} from 'platform/detection'
+import {RoutesContainer, FlatNavigator} from '../../Navigation'
+
+const ShellInner = observer(() => {
+  const store = useStores()
+
+  return (
+    <>
+      <View style={s.hContentRegion}>
+        <ErrorBoundary>
+          <FlatNavigator />
+        </ErrorBoundary>
+      </View>
+      <DesktopLeftNav />
+      <DesktopRightNav />
+      <View style={[styles.viewBorder, styles.viewBorderLeft]} />
+      <View style={[styles.viewBorder, styles.viewBorderRight]} />
+      <Composer
+        active={store.shell.isComposerActive}
+        onClose={() => store.shell.closeComposer()}
+        winHeight={0}
+        replyTo={store.shell.composerOpts?.replyTo}
+        onPost={store.shell.composerOpts?.onPost}
+      />
+      <ModalsContainer />
+      <Lightbox />
+    </>
+  )
+})
+
+export const Shell: React.FC = observer(() => {
+  const pageBg = useColorSchemeStyle(styles.bgLight, styles.bgDark)
+  const store = useStores()
+
+  if (isMobileWeb) {
+    return <NoMobileWeb />
+  }
+
+  if (!store.session.hasSession) {
+    return (
+      <View style={[s.hContentRegion, pageBg]}>
+        <Login />
+        <ModalsContainer />
+      </View>
+    )
+  }
+
+  return (
+    <View style={[s.hContentRegion, pageBg]}>
+      <RoutesContainer>
+        <ShellInner />
+      </RoutesContainer>
+    </View>
+  )
+})
+
+function NoMobileWeb() {
+  const pal = usePalette('default')
+  return (
+    <View style={[pal.view, styles.noMobileWeb]}>
+      <Text type="title-2xl" style={s.pb20}>
+        We're so sorry!
+      </Text>
+      <Text type="lg">
+        This app is not available for mobile Web yet. Please open it on your
+        desktop or download the iOS app.
+      </Text>
+    </View>
+  )
+}
+
+const styles = StyleSheet.create({
+  bgLight: {
+    backgroundColor: colors.white,
+  },
+  bgDark: {
+    backgroundColor: colors.black, // TODO
+  },
+  viewBorder: {
+    position: 'absolute',
+    width: 1,
+    height: '100%',
+    borderLeftWidth: 1,
+    borderLeftColor: colors.gray2,
+  },
+  viewBorderLeft: {
+    left: 'calc(50vw - 300px)',
+  },
+  viewBorderRight: {
+    left: 'calc(50vw + 300px)',
+  },
+  noMobileWeb: {
+    height: '100%',
+    justifyContent: 'center',
+    paddingHorizontal: 20,
+    paddingBottom: 40,
+  },
+})
diff --git a/src/view/shell/mobile/Menu.tsx b/src/view/shell/mobile/Menu.tsx
deleted file mode 100644
index 927e712e1..000000000
--- a/src/view/shell/mobile/Menu.tsx
+++ /dev/null
@@ -1,354 +0,0 @@
-import React from 'react'
-import {
-  Linking,
-  StyleProp,
-  StyleSheet,
-  TouchableOpacity,
-  View,
-  ViewStyle,
-} from 'react-native'
-import {observer} from 'mobx-react-lite'
-import {
-  FontAwesomeIcon,
-  FontAwesomeIconStyle,
-} from '@fortawesome/react-native-fontawesome'
-import {s, colors} from 'lib/styles'
-import {FEEDBACK_FORM_URL} from 'lib/constants'
-import {useStores} from 'state/index'
-import {
-  HomeIcon,
-  HomeIconSolid,
-  BellIcon,
-  BellIconSolid,
-  UserIcon,
-  CogIcon,
-  MagnifyingGlassIcon2,
-  MagnifyingGlassIcon2Solid,
-  MoonIcon,
-} from 'lib/icons'
-import {TabPurpose, TabPurposeMainPath} from 'state/models/navigation'
-import {UserAvatar} from '../../com/util/UserAvatar'
-import {Text} from '../../com/util/text/Text'
-import {useTheme} from 'lib/ThemeContext'
-import {usePalette} from 'lib/hooks/usePalette'
-import {useAnalytics} from 'lib/analytics'
-import {pluralize} from 'lib/strings/helpers'
-
-export const Menu = observer(({onClose}: {onClose: () => void}) => {
-  const theme = useTheme()
-  const pal = usePalette('default')
-  const store = useStores()
-  const {track} = useAnalytics()
-
-  // events
-  // =
-
-  const onNavigate = (url: string) => {
-    track('Menu:ItemClicked', {url})
-
-    onClose()
-    if (url === TabPurposeMainPath[TabPurpose.Notifs]) {
-      store.nav.switchTo(TabPurpose.Notifs, true)
-    } else if (url === TabPurposeMainPath[TabPurpose.Search]) {
-      store.nav.switchTo(TabPurpose.Search, true)
-    } else {
-      store.nav.switchTo(TabPurpose.Default, true)
-      if (url !== '/') {
-        store.nav.navigate(url)
-      }
-    }
-  }
-
-  const onPressFeedback = () => {
-    track('Menu:FeedbackClicked')
-    Linking.openURL(FEEDBACK_FORM_URL)
-  }
-
-  // rendering
-  // =
-
-  const MenuItem = ({
-    icon,
-    label,
-    count,
-    url,
-    bold,
-    onPress,
-  }: {
-    icon: JSX.Element
-    label: string
-    count?: number
-    url?: string
-    bold?: boolean
-    onPress?: () => void
-  }) => (
-    <TouchableOpacity
-      testID={`menuItemButton-${label}`}
-      style={styles.menuItem}
-      onPress={onPress ? onPress : () => onNavigate(url || '/')}>
-      <View style={[styles.menuItemIconWrapper]}>
-        {icon}
-        {count ? (
-          <View style={styles.menuItemCount}>
-            <Text style={styles.menuItemCountLabel}>{count}</Text>
-          </View>
-        ) : undefined}
-      </View>
-      <Text
-        type={bold ? '2xl-bold' : '2xl'}
-        style={[pal.text, s.flex1]}
-        numberOfLines={1}>
-        {label}
-      </Text>
-    </TouchableOpacity>
-  )
-
-  const onDarkmodePress = () => {
-    track('Menu:ItemClicked', {url: '/darkmode'})
-    store.shell.setDarkMode(!store.shell.darkMode)
-  }
-
-  const isAtHome =
-    store.nav.tab.current.url === TabPurposeMainPath[TabPurpose.Default]
-  const isAtSearch =
-    store.nav.tab.current.url === TabPurposeMainPath[TabPurpose.Search]
-  const isAtNotifications =
-    store.nav.tab.current.url === TabPurposeMainPath[TabPurpose.Notifs]
-
-  return (
-    <View
-      testID="menuView"
-      style={[
-        styles.view,
-        theme.colorScheme === 'light' ? pal.view : styles.viewDarkMode,
-      ]}>
-      <TouchableOpacity
-        testID="profileCardButton"
-        onPress={() => onNavigate(`/profile/${store.me.handle}`)}>
-        <UserAvatar
-          size={80}
-          displayName={store.me.displayName}
-          handle={store.me.handle}
-          avatar={store.me.avatar}
-        />
-        <Text
-          type="title-lg"
-          style={[pal.text, s.bold, styles.profileCardDisplayName]}>
-          {store.me.displayName || store.me.handle}
-        </Text>
-        <Text type="2xl" style={[pal.textLight, styles.profileCardHandle]}>
-          @{store.me.handle}
-        </Text>
-        <Text type="xl" style={[pal.textLight, styles.profileCardFollowers]}>
-          <Text type="xl-medium" style={pal.text}>
-            {store.me.followersCount || 0}
-          </Text>{' '}
-          {pluralize(store.me.followersCount || 0, 'follower')} &middot;{' '}
-          <Text type="xl-medium" style={pal.text}>
-            {store.me.followsCount || 0}
-          </Text>{' '}
-          following
-        </Text>
-      </TouchableOpacity>
-      <View style={s.flex1} />
-      <View>
-        <MenuItem
-          icon={
-            isAtSearch ? (
-              <MagnifyingGlassIcon2Solid
-                style={pal.text as StyleProp<ViewStyle>}
-                size={24}
-                strokeWidth={1.7}
-              />
-            ) : (
-              <MagnifyingGlassIcon2
-                style={pal.text as StyleProp<ViewStyle>}
-                size={24}
-                strokeWidth={1.7}
-              />
-            )
-          }
-          label="Search"
-          url="/search"
-          bold={isAtSearch}
-        />
-        <MenuItem
-          icon={
-            isAtHome ? (
-              <HomeIconSolid
-                style={pal.text as StyleProp<ViewStyle>}
-                size="24"
-                strokeWidth={3.25}
-                fillOpacity={1}
-              />
-            ) : (
-              <HomeIcon
-                style={pal.text as StyleProp<ViewStyle>}
-                size="24"
-                strokeWidth={3.25}
-              />
-            )
-          }
-          label="Home"
-          url="/"
-          bold={isAtHome}
-        />
-        <MenuItem
-          icon={
-            isAtNotifications ? (
-              <BellIconSolid
-                style={pal.text as StyleProp<ViewStyle>}
-                size="24"
-                strokeWidth={1.7}
-                fillOpacity={1}
-              />
-            ) : (
-              <BellIcon
-                style={pal.text as StyleProp<ViewStyle>}
-                size="24"
-                strokeWidth={1.7}
-              />
-            )
-          }
-          label="Notifications"
-          url="/notifications"
-          count={store.me.notifications.unreadCount}
-          bold={isAtNotifications}
-        />
-        <MenuItem
-          icon={
-            <UserIcon
-              style={pal.text as StyleProp<ViewStyle>}
-              size="26"
-              strokeWidth={1.5}
-            />
-          }
-          label="Profile"
-          url={`/profile/${store.me.handle}`}
-        />
-        <MenuItem
-          icon={
-            <CogIcon
-              style={pal.text as StyleProp<ViewStyle>}
-              size="26"
-              strokeWidth={1.75}
-            />
-          }
-          label="Settings"
-          url="/settings"
-        />
-      </View>
-      <View style={s.flex1} />
-      <View style={styles.footer}>
-        <TouchableOpacity
-          onPress={onDarkmodePress}
-          style={[
-            styles.footerBtn,
-            theme.colorScheme === 'light' ? pal.btn : styles.footerBtnDarkMode,
-          ]}>
-          <MoonIcon
-            size={22}
-            style={pal.text as StyleProp<ViewStyle>}
-            strokeWidth={2}
-          />
-        </TouchableOpacity>
-        <TouchableOpacity
-          onPress={onPressFeedback}
-          style={[
-            styles.footerBtn,
-            styles.footerBtnFeedback,
-            theme.colorScheme === 'light'
-              ? styles.footerBtnFeedbackLight
-              : styles.footerBtnFeedbackDark,
-          ]}>
-          <FontAwesomeIcon
-            style={pal.link as FontAwesomeIconStyle}
-            size={19}
-            icon={['far', 'message']}
-          />
-          <Text type="2xl-medium" style={[pal.link, s.pl10]}>
-            Feedback
-          </Text>
-        </TouchableOpacity>
-      </View>
-    </View>
-  )
-})
-
-const styles = StyleSheet.create({
-  view: {
-    flex: 1,
-    paddingTop: 20,
-    paddingBottom: 50,
-    paddingLeft: 30,
-  },
-  viewDarkMode: {
-    backgroundColor: '#1B1919',
-  },
-
-  profileCardDisplayName: {
-    marginTop: 20,
-    paddingRight: 20,
-  },
-  profileCardHandle: {
-    marginTop: 4,
-    paddingRight: 20,
-  },
-  profileCardFollowers: {
-    marginTop: 16,
-    paddingRight: 20,
-  },
-
-  menuItem: {
-    flexDirection: 'row',
-    alignItems: 'center',
-    paddingVertical: 16,
-    paddingRight: 10,
-  },
-  menuItemIconWrapper: {
-    width: 24,
-    height: 24,
-    alignItems: 'center',
-    justifyContent: 'center',
-    marginRight: 12,
-  },
-  menuItemCount: {
-    position: 'absolute',
-    right: -6,
-    top: -2,
-    backgroundColor: colors.red3,
-    paddingHorizontal: 4,
-    paddingBottom: 1,
-    borderRadius: 6,
-  },
-  menuItemCountLabel: {
-    fontSize: 12,
-    fontWeight: 'bold',
-    color: colors.white,
-  },
-
-  footer: {
-    flexDirection: 'row',
-    justifyContent: 'space-between',
-    paddingRight: 30,
-    paddingTop: 80,
-  },
-  footerBtn: {
-    flexDirection: 'row',
-    alignItems: 'center',
-    padding: 10,
-    borderRadius: 25,
-  },
-  footerBtnDarkMode: {
-    backgroundColor: colors.black,
-  },
-  footerBtnFeedback: {
-    paddingHorizontal: 24,
-  },
-  footerBtnFeedbackLight: {
-    backgroundColor: '#DDEFFF',
-  },
-  footerBtnFeedbackDark: {
-    backgroundColor: colors.blue6,
-  },
-})
diff --git a/src/view/shell/mobile/index.tsx b/src/view/shell/mobile/index.tsx
deleted file mode 100644
index 01df6c165..000000000
--- a/src/view/shell/mobile/index.tsx
+++ /dev/null
@@ -1,335 +0,0 @@
-import React, {useState} from 'react'
-import {observer} from 'mobx-react-lite'
-import {
-  Animated,
-  StatusBar,
-  StyleSheet,
-  TouchableWithoutFeedback,
-  useWindowDimensions,
-  View,
-} from 'react-native'
-import {ScreenContainer, Screen} from 'react-native-screens'
-import {useSafeAreaInsets} from 'react-native-safe-area-context'
-import {IconProp} from '@fortawesome/fontawesome-svg-core'
-import {useStores} from 'state/index'
-import {NavigationModel} from 'state/models/navigation'
-import {match, MatchResult} from '../../routes'
-import {Login} from '../../screens/Login'
-import {Menu} from './Menu'
-import {BottomBar} from './BottomBar'
-import {HorzSwipe} from '../../com/util/gestures/HorzSwipe'
-import {ModalsContainer} from '../../com/modals/Modal'
-import {Lightbox} from '../../com/lightbox/Lightbox'
-import {Text} from '../../com/util/text/Text'
-import {ErrorBoundary} from '../../com/util/ErrorBoundary'
-import {Composer} from './Composer'
-import {s, colors} from 'lib/styles'
-import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
-import {useTheme} from 'lib/ThemeContext'
-import {usePalette} from 'lib/hooks/usePalette'
-
-export const MobileShell: React.FC = observer(() => {
-  const theme = useTheme()
-  const pal = usePalette('default')
-  const store = useStores()
-  const winDim = useWindowDimensions()
-  const [menuSwipingDirection, setMenuSwipingDirection] = useState(0)
-  const swipeGestureInterp = useAnimatedValue(0)
-  const safeAreaInsets = useSafeAreaInsets()
-  const screenRenderDesc = constructScreenRenderDesc(store.nav)
-
-  // navigation swipes
-  // =
-  const isMenuActive = store.shell.isMainMenuOpen
-  const canSwipeLeft = store.nav.tab.canGoBack || !isMenuActive
-  const canSwipeRight = isMenuActive
-  const onNavSwipeStartDirection = (dx: number) => {
-    if (dx < 0 && !store.nav.tab.canGoBack) {
-      setMenuSwipingDirection(dx)
-    } else if (dx > 0 && isMenuActive) {
-      setMenuSwipingDirection(dx)
-    } else {
-      setMenuSwipingDirection(0)
-    }
-  }
-  const onNavSwipeEnd = (dx: number) => {
-    if (dx < 0) {
-      if (store.nav.tab.canGoBack) {
-        store.nav.tab.goBack()
-      } else {
-        store.shell.setMainMenuOpen(true)
-      }
-    } else if (dx > 0) {
-      if (isMenuActive) {
-        store.shell.setMainMenuOpen(false)
-      }
-    }
-    setMenuSwipingDirection(0)
-  }
-  const swipeTranslateX = Animated.multiply(
-    swipeGestureInterp,
-    winDim.width * -1,
-  )
-  const swipeTransform = store.nav.tab.canGoBack
-    ? {transform: [{translateX: swipeTranslateX}]}
-    : undefined
-  let shouldRenderMenu = false
-  let menuTranslateX
-  const menuDrawerWidth = winDim.width - 100
-  if (isMenuActive) {
-    // menu is active, interpret swipes as closes
-    menuTranslateX = Animated.multiply(swipeGestureInterp, menuDrawerWidth * -1)
-    shouldRenderMenu = true
-  } else if (!store.nav.tab.canGoBack) {
-    // at back of history, interpret swipes as opens
-    menuTranslateX = Animated.subtract(
-      menuDrawerWidth * -1,
-      Animated.multiply(swipeGestureInterp, menuDrawerWidth),
-    )
-    shouldRenderMenu = true
-  }
-  const menuSwipeTransform = menuTranslateX
-    ? {
-        transform: [{translateX: menuTranslateX}],
-      }
-    : undefined
-  const swipeOpacity = {
-    opacity: swipeGestureInterp.interpolate({
-      inputRange: [-1, 0, 1],
-      outputRange: [0, 0.6, 0],
-    }),
-  }
-  const menuSwipeOpacity =
-    menuSwipingDirection !== 0
-      ? {
-          opacity: swipeGestureInterp.interpolate({
-            inputRange: menuSwipingDirection > 0 ? [0, 1] : [-1, 0],
-            outputRange: [0.6, 0],
-          }),
-        }
-      : undefined
-
-  if (store.hackUpgradeNeeded) {
-    return (
-      <View style={styles.outerContainer}>
-        <View style={[s.flexCol, s.p20, s.h100pct]}>
-          <View style={s.flex1} />
-          <View>
-            <Text type="title-2xl" style={s.pb10}>
-              Update required
-            </Text>
-            <Text style={[s.pb20, s.bold]}>
-              Please update your app to the latest version. If no update is
-              available yet, please check the App Store in a day or so.
-            </Text>
-            <Text type="title" style={s.pb10}>
-              What's happening?
-            </Text>
-            <Text style={s.pb10}>
-              We're in the final stages of the AT Protocol's v1 development. To
-              make sure everything works as well as possible, we're making final
-              breaking changes to the APIs.
-            </Text>
-            <Text>
-              If we didn't botch this process, a new version of the app should
-              be available now.
-            </Text>
-          </View>
-          <View style={s.flex1} />
-          <View style={s.footerSpacer} />
-        </View>
-      </View>
-    )
-  }
-
-  if (!store.session.hasSession) {
-    return (
-      <View style={styles.outerContainer}>
-        <StatusBar
-          barStyle={
-            theme.colorScheme === 'dark' ? 'light-content' : 'dark-content'
-          }
-        />
-        <Login />
-        <ModalsContainer />
-      </View>
-    )
-  }
-
-  const screenBg = {
-    backgroundColor: theme.colorScheme === 'dark' ? colors.black : colors.gray1,
-  }
-  return (
-    <View testID="mobileShellView" style={[styles.outerContainer, pal.view]}>
-      <StatusBar
-        barStyle={
-          theme.colorScheme === 'dark' ? 'light-content' : 'dark-content'
-        }
-      />
-      <View style={[styles.innerContainer, {paddingTop: safeAreaInsets.top}]}>
-        <HorzSwipe
-          distThresholdDivisor={2.5}
-          useNativeDriver
-          panX={swipeGestureInterp}
-          swipeEnabled
-          canSwipeLeft={canSwipeLeft}
-          canSwipeRight={canSwipeRight}
-          onSwipeStartDirection={onNavSwipeStartDirection}
-          onSwipeEnd={onNavSwipeEnd}>
-          <ScreenContainer style={styles.screenContainer}>
-            {screenRenderDesc.screens.map(
-              ({Com, navIdx, params, key, current, previous}) => {
-                if (isMenuActive) {
-                  // HACK menu is active, treat current as previous
-                  if (previous) {
-                    previous = false
-                  } else if (current) {
-                    current = false
-                    previous = true
-                  }
-                }
-                return (
-                  <Screen
-                    key={key}
-                    style={[StyleSheet.absoluteFill]}
-                    activityState={current ? 2 : previous ? 1 : 0}>
-                    <Animated.View
-                      style={
-                        current ? [styles.screenMask, swipeOpacity] : undefined
-                      }
-                    />
-                    <Animated.View
-                      style={[
-                        s.h100pct,
-                        screenBg,
-                        current ? [swipeTransform] : undefined,
-                      ]}>
-                      <ErrorBoundary>
-                        <Com
-                          params={params}
-                          navIdx={navIdx}
-                          visible={current}
-                        />
-                      </ErrorBoundary>
-                    </Animated.View>
-                  </Screen>
-                )
-              },
-            )}
-          </ScreenContainer>
-          <BottomBar />
-          {isMenuActive || menuSwipingDirection !== 0 ? (
-            <TouchableWithoutFeedback
-              onPress={() => store.shell.setMainMenuOpen(false)}>
-              <Animated.View style={[styles.screenMask, menuSwipeOpacity]} />
-            </TouchableWithoutFeedback>
-          ) : undefined}
-          {shouldRenderMenu && (
-            <Animated.View style={[styles.menuDrawer, menuSwipeTransform]}>
-              <Menu onClose={() => store.shell.setMainMenuOpen(false)} />
-            </Animated.View>
-          )}
-        </HorzSwipe>
-      </View>
-      <ModalsContainer />
-      <Lightbox />
-      <Composer
-        active={store.shell.isComposerActive}
-        onClose={() => store.shell.closeComposer()}
-        winHeight={winDim.height}
-        replyTo={store.shell.composerOpts?.replyTo}
-        imagesOpen={store.shell.composerOpts?.imagesOpen}
-        onPost={store.shell.composerOpts?.onPost}
-        quote={store.shell.composerOpts?.quote}
-      />
-    </View>
-  )
-})
-
-/**
- * This method produces the information needed by the shell to
- * render the current screens with screen-caching behaviors.
- */
-type ScreenRenderDesc = MatchResult & {
-  key: string
-  navIdx: string
-  current: boolean
-  previous: boolean
-  isNewTab: boolean
-}
-function constructScreenRenderDesc(nav: NavigationModel): {
-  icon: IconProp
-  hasNewTab: boolean
-  screens: ScreenRenderDesc[]
-} {
-  let hasNewTab = false
-  let icon: IconProp = 'magnifying-glass'
-  let screens: ScreenRenderDesc[] = []
-  for (const tab of nav.tabs) {
-    const tabScreens = [
-      ...tab.getBackList(5),
-      Object.assign({}, tab.current, {index: tab.index}),
-    ]
-    const parsedTabScreens = tabScreens.map(screen => {
-      const isCurrent = nav.isCurrentScreen(tab.id, screen.index)
-      const isPrevious = nav.isCurrentScreen(tab.id, screen.index + 1)
-      const matchRes = match(screen.url)
-      if (isCurrent) {
-        icon = matchRes.icon
-      }
-      hasNewTab = hasNewTab || tab.isNewTab
-      return Object.assign(matchRes, {
-        key: `t${tab.id}-s${screen.index}`,
-        navIdx: `${tab.id}-${screen.id}`,
-        current: isCurrent,
-        previous: isPrevious,
-        isNewTab: tab.isNewTab,
-      }) as ScreenRenderDesc
-    })
-    screens = screens.concat(parsedTabScreens)
-  }
-  return {
-    icon,
-    hasNewTab,
-    screens,
-  }
-}
-
-const styles = StyleSheet.create({
-  outerContainer: {
-    height: '100%',
-  },
-  innerContainer: {
-    height: '100%',
-  },
-  screenContainer: {
-    height: '100%',
-  },
-  screenMask: {
-    position: 'absolute',
-    top: 0,
-    bottom: 0,
-    left: 0,
-    right: 0,
-    backgroundColor: '#000',
-    opacity: 0.6,
-  },
-  menuDrawer: {
-    position: 'absolute',
-    top: 0,
-    bottom: 0,
-    left: 0,
-    right: 100,
-  },
-  topBarProtector: {
-    position: 'absolute',
-    top: 0,
-    left: 0,
-    right: 0,
-    height: 50, // will be overwritten by insets
-    backgroundColor: colors.white,
-  },
-  topBarProtectorDark: {
-    backgroundColor: colors.black,
-  },
-})
diff --git a/src/view/shell/web/DesktopHeader.tsx b/src/view/shell/web/DesktopHeader.tsx
deleted file mode 100644
index 8748ebbde..000000000
--- a/src/view/shell/web/DesktopHeader.tsx
+++ /dev/null
@@ -1,222 +0,0 @@
-import React from 'react'
-import {observer} from 'mobx-react-lite'
-import {Pressable, StyleSheet, TouchableOpacity, View} from 'react-native'
-import {Text} from 'view/com/util/text/Text'
-import {UserAvatar} from 'view/com/util/UserAvatar'
-import {usePalette} from 'lib/hooks/usePalette'
-import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle'
-import {useStores} from 'state/index'
-import {colors} from 'lib/styles'
-import {
-  ComposeIcon,
-  HomeIcon,
-  HomeIconSolid,
-  BellIcon,
-  BellIconSolid,
-  MagnifyingGlassIcon,
-  CogIcon,
-} from 'lib/icons'
-import {DesktopSearch} from './DesktopSearch'
-
-interface NavItemProps {
-  count?: number
-  href: string
-  icon: JSX.Element
-  iconFilled: JSX.Element
-  isProfile?: boolean
-}
-export const NavItem = observer(
-  ({count, href, icon, iconFilled}: NavItemProps) => {
-    const store = useStores()
-    const hoverBg = useColorSchemeStyle(
-      styles.navItemHoverBgLight,
-      styles.navItemHoverBgDark,
-    )
-    const isCurrent = store.nav.tab.current.url === href
-    const onPress = () => store.nav.navigate(href)
-    return (
-      <Pressable
-        style={state => [
-          styles.navItem,
-          // @ts-ignore Pressable state differs for RNW -prf
-          (state.hovered || isCurrent) && hoverBg,
-        ]}
-        onPress={onPress}>
-        <View style={[styles.navItemIconWrapper]}>
-          {isCurrent ? iconFilled : icon}
-          {typeof count === 'number' && count > 0 && (
-            <Text type="button" style={styles.navItemCount}>
-              {count}
-            </Text>
-          )}
-        </View>
-      </Pressable>
-    )
-  },
-)
-
-export const ProfileItem = observer(() => {
-  const store = useStores()
-  const hoverBg = useColorSchemeStyle(
-    styles.navItemHoverBgLight,
-    styles.navItemHoverBgDark,
-  )
-  const href = `/profile/${store.me.handle}`
-  const isCurrent = store.nav.tab.current.url === href
-  const onPress = () => store.nav.navigate(href)
-  return (
-    <Pressable
-      style={state => [
-        styles.navItem,
-        // @ts-ignore Pressable state differs for RNW -prf
-        (state.hovered || isCurrent) && hoverBg,
-      ]}
-      onPress={onPress}>
-      <View style={[styles.navItemIconWrapper]}>
-        <UserAvatar
-          handle={store.me.handle}
-          displayName={store.me.displayName}
-          avatar={store.me.avatar}
-          size={28}
-        />
-      </View>
-    </Pressable>
-  )
-})
-
-export const DesktopHeader = observer(function DesktopHeader({}: {
-  canGoBack?: boolean
-}) {
-  const store = useStores()
-  const pal = usePalette('default')
-  const onPressCompose = () => store.shell.openComposer({})
-
-  return (
-    <View style={[styles.header, pal.borderDark, pal.view]}>
-      <Text type="title-xl" style={[pal.text, styles.title]}>
-        Bluesky
-      </Text>
-      <View style={styles.space30} />
-      <NavItem
-        href="/"
-        icon={<HomeIcon size={24} />}
-        iconFilled={<HomeIconSolid size={24} />}
-      />
-      <View style={styles.space15} />
-      <NavItem
-        href="/search"
-        icon={<MagnifyingGlassIcon size={24} />}
-        iconFilled={<MagnifyingGlassIcon strokeWidth={3} size={24} />}
-      />
-      <View style={styles.space15} />
-      <NavItem
-        href="/notifications"
-        count={store.me.notifications.unreadCount}
-        icon={<BellIcon size={24} />}
-        iconFilled={<BellIconSolid size={24} />}
-      />
-      <View style={styles.spaceFlex} />
-      <TouchableOpacity style={[styles.newPostBtn]} onPress={onPressCompose}>
-        <View style={styles.newPostBtnIconWrapper}>
-          <ComposeIcon
-            size={16}
-            strokeWidth={2}
-            style={styles.newPostBtnLabel}
-          />
-        </View>
-        <Text type="md" style={styles.newPostBtnLabel}>
-          New Post
-        </Text>
-      </TouchableOpacity>
-      <View style={styles.space20} />
-      <DesktopSearch />
-      <View style={styles.space15} />
-      <ProfileItem />
-      <NavItem
-        href="/settings"
-        icon={<CogIcon strokeWidth={2} size={28} />}
-        iconFilled={<CogIcon strokeWidth={2.5} size={28} />}
-      />
-    </View>
-  )
-})
-
-const styles = StyleSheet.create({
-  header: {
-    flexDirection: 'row',
-    alignItems: 'center',
-    // paddingTop: 18,
-    // paddingBottom: 18,
-    paddingLeft: 30,
-    paddingRight: 40,
-    borderBottomWidth: 1,
-    zIndex: 1,
-  },
-
-  spaceFlex: {
-    flex: 1,
-  },
-  space15: {
-    width: 15,
-  },
-  space20: {
-    width: 20,
-  },
-  space30: {
-    width: 30,
-  },
-
-  title: {},
-
-  navItem: {
-    paddingTop: 14,
-    paddingBottom: 10,
-    paddingHorizontal: 10,
-    alignItems: 'center',
-    borderBottomWidth: 2,
-    borderBottomColor: 'transparent',
-  },
-  navItemHoverBgLight: {
-    borderBottomWidth: 2,
-    borderBottomColor: colors.blue3,
-  },
-  navItemHoverBgDark: {
-    borderBottomWidth: 2,
-    backgroundColor: colors.blue3,
-  },
-  navItemIconWrapper: {
-    alignItems: 'center',
-    justifyContent: 'center',
-    width: 28,
-    height: 28,
-    marginBottom: 2,
-  },
-  navItemCount: {
-    position: 'absolute',
-    top: 0,
-    left: 15,
-    backgroundColor: colors.red3,
-    color: colors.white,
-    fontSize: 12,
-    fontWeight: 'bold',
-    paddingHorizontal: 4,
-    borderRadius: 6,
-  },
-
-  newPostBtn: {
-    flexDirection: 'row',
-    alignItems: 'center',
-    justifyContent: 'center',
-    borderRadius: 24,
-    paddingTop: 8,
-    paddingBottom: 8,
-    paddingHorizontal: 18,
-    backgroundColor: colors.blue3,
-  },
-  newPostBtnIconWrapper: {
-    marginRight: 8,
-  },
-  newPostBtnLabel: {
-    color: colors.white,
-  },
-})
diff --git a/src/view/shell/web/index.tsx b/src/view/shell/web/index.tsx
deleted file mode 100644
index a76ae8060..000000000
--- a/src/view/shell/web/index.tsx
+++ /dev/null
@@ -1,150 +0,0 @@
-import React from 'react'
-import {observer} from 'mobx-react-lite'
-import {View, StyleSheet} from 'react-native'
-import {IconProp} from '@fortawesome/fontawesome-svg-core'
-import {useStores} from 'state/index'
-import {NavigationModel} from 'state/models/navigation'
-import {match, MatchResult} from '../../routes'
-import {DesktopHeader} from './DesktopHeader'
-import {Login} from '../../screens/Login'
-import {ErrorBoundary} from '../../com/util/ErrorBoundary'
-import {Lightbox} from '../../com/lightbox/Lightbox'
-import {ModalsContainer} from '../../com/modals/Modal'
-import {Text} from 'view/com/util/text/Text'
-import {Composer} from './Composer'
-import {usePalette} from 'lib/hooks/usePalette'
-import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle'
-import {s, colors} from 'lib/styles'
-import {isMobileWeb} from 'platform/detection'
-
-export const WebShell: React.FC = observer(() => {
-  const pageBg = useColorSchemeStyle(styles.bgLight, styles.bgDark)
-  const store = useStores()
-  const screenRenderDesc = constructScreenRenderDesc(store.nav)
-
-  if (isMobileWeb) {
-    return <NoMobileWeb />
-  }
-
-  if (!store.session.hasSession) {
-    return (
-      <View style={styles.outerContainer}>
-        <Login />
-        <ModalsContainer />
-      </View>
-    )
-  }
-
-  return (
-    <View style={[styles.outerContainer, pageBg]}>
-      <DesktopHeader />
-      {screenRenderDesc.screens.map(({Com, navIdx, params, key, current}) => (
-        <View
-          key={key}
-          style={[s.hContentRegion, current ? styles.visible : styles.hidden]}>
-          <ErrorBoundary>
-            <Com params={params} navIdx={navIdx} visible={current} />
-          </ErrorBoundary>
-        </View>
-      ))}
-      <Composer
-        active={store.shell.isComposerActive}
-        onClose={() => store.shell.closeComposer()}
-        winHeight={0}
-        replyTo={store.shell.composerOpts?.replyTo}
-        imagesOpen={store.shell.composerOpts?.imagesOpen}
-        onPost={store.shell.composerOpts?.onPost}
-      />
-      <ModalsContainer />
-      <Lightbox />
-    </View>
-  )
-})
-
-/**
- * This method produces the information needed by the shell to
- * render the current screens with screen-caching behaviors.
- */
-type ScreenRenderDesc = MatchResult & {
-  key: string
-  navIdx: string
-  current: boolean
-  previous: boolean
-  isNewTab: boolean
-}
-function constructScreenRenderDesc(nav: NavigationModel): {
-  icon: IconProp
-  hasNewTab: boolean
-  screens: ScreenRenderDesc[]
-} {
-  let hasNewTab = false
-  let icon: IconProp = 'magnifying-glass'
-  let screens: ScreenRenderDesc[] = []
-  for (const tab of nav.tabs) {
-    const tabScreens = [
-      ...tab.getBackList(5),
-      Object.assign({}, tab.current, {index: tab.index}),
-    ]
-    const parsedTabScreens = tabScreens.map(screen => {
-      const isCurrent = nav.isCurrentScreen(tab.id, screen.index)
-      const isPrevious = nav.isCurrentScreen(tab.id, screen.index + 1)
-      const matchRes = match(screen.url)
-      if (isCurrent) {
-        icon = matchRes.icon
-      }
-      hasNewTab = hasNewTab || tab.isNewTab
-      return Object.assign(matchRes, {
-        key: `t${tab.id}-s${screen.index}`,
-        navIdx: `${tab.id}-${screen.id}`,
-        current: isCurrent,
-        previous: isPrevious,
-        isNewTab: tab.isNewTab,
-      }) as ScreenRenderDesc
-    })
-    screens = screens.concat(parsedTabScreens)
-  }
-  return {
-    icon,
-    hasNewTab,
-    screens,
-  }
-}
-
-function NoMobileWeb() {
-  const pal = usePalette('default')
-  return (
-    <View style={[pal.view, styles.noMobileWeb]}>
-      <Text type="title-2xl" style={s.pb20}>
-        We're so sorry!
-      </Text>
-      <Text type="lg">
-        This app is not available for mobile Web yet. Please open it on your
-        desktop or download the iOS app.
-      </Text>
-    </View>
-  )
-}
-
-const styles = StyleSheet.create({
-  outerContainer: {
-    height: '100%',
-  },
-  bgLight: {
-    backgroundColor: colors.white,
-  },
-  bgDark: {
-    backgroundColor: colors.black, // TODO
-  },
-  visible: {
-    display: 'flex',
-  },
-  hidden: {
-    display: 'none',
-  },
-  noMobileWeb: {
-    height: '100%',
-    justifyContent: 'center',
-    paddingHorizontal: 20,
-    paddingBottom: 40,
-  },
-})
diff --git a/web/index.html b/web/index.html
new file mode 100644
index 000000000..9e05b516b
--- /dev/null
+++ b/web/index.html
@@ -0,0 +1,175 @@
+<!DOCTYPE html>
+<html lang="%LANG_ISO_CODE%">
+  <head>
+    <meta charset="utf-8" />
+    <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
+    <!-- 
+      This viewport works for phones with notches.
+      It's optimized for gestures by disabling global zoom.
+     -->
+    <meta
+      name="viewport"
+      content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover"
+    />
+    <title>%WEB_TITLE%</title>
+    <style>
+      /**
+       * Extend the react-native-web reset:
+       * https://github.com/necolas/react-native-web/blob/master/packages/react-native-web/src/exports/StyleSheet/initialRules.js
+       */
+      html,
+      body,
+      #root {
+        width: 100%;
+        /* To smooth any scrolling behavior */
+        -webkit-overflow-scrolling: touch;
+        margin: 0px;
+        padding: 0px;
+        /* Allows content to fill the viewport and go beyond the bottom */
+        min-height: 100%;
+      }
+      #root {
+        flex-shrink: 0;
+        flex-basis: auto;
+        flex-grow: 1;
+        display: flex;
+        flex: 1;
+      }
+
+      html {
+        scroll-behavior: smooth;
+        /* Prevent text size change on orientation change https://gist.github.com/tfausak/2222823#file-ios-8-web-app-html-L138 */
+        -webkit-text-size-adjust: 100%;
+        height: calc(100% + env(safe-area-inset-top));
+      }
+
+      body {
+        display: flex;
+        /* Allows you to scroll below the viewport; default value is visible */
+        overflow-y: auto;
+        overscroll-behavior-y: none;
+        text-rendering: optimizeLegibility;
+        -webkit-font-smoothing: antialiased;
+        -moz-osx-font-smoothing: grayscale;
+        -ms-overflow-style: scrollbar;
+      }
+      /* Enable for apps that support dark-theme */
+      /*@media (prefers-color-scheme: dark) {
+        body {
+          background-color: black;
+        }
+      }*/
+
+      /* Remove focus state on inputs */
+      *:focus {
+        outline: 0;
+      }
+      /* Remove default link styling */
+      a {
+        color: inherit;
+      }
+      a[role="link"]:hover {
+        text-decoration: underline;
+      }
+
+      /* ProseMirror */
+      .ProseMirror {
+        font: 18px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
+        min-height: 140px;
+      }
+      .ProseMirror p {
+        margin: 0;
+      }
+      .ProseMirror p.is-editor-empty:first-child::before {
+        color: #8d8e96;
+        content: attr(data-placeholder);
+        float: left;
+        height: 0;
+        pointer-events: none;
+      }
+      .ProseMirror .mention {
+        color: #0085ff;
+      }
+      .ProseMirror a {
+        color: #0085ff;
+        cursor: pointer;
+      }
+      .tippy-content .items {
+        border-radius: 6px;
+        background: #F3F3F8;
+        border: 1px solid #e0d9d9;
+        padding: 3px 3px;
+      }
+      .tippy-content .items .item {
+        display: block;
+        background: transparent;
+        color: #8a8c9a;
+        border: 0;
+        font: 17px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
+        padding: 7px 10px 8px;
+        width: 100%;
+        text-align: left;
+        box-sizing: border-box;
+        letter-spacing: 0.2px;
+      }
+      .tippy-content .items .item.is-selected {
+        background: #fff;
+        border-radius: 4px;
+        color: #333;
+      }
+    </style>
+  </head>
+
+  <body>
+    <!-- 
+      A generic no script element with a reload button and a message.
+      Feel free to customize this however you'd like.
+    -->
+    <noscript>
+      <form
+        action=""
+        style="
+          background-color: #fff;
+          position: fixed;
+          top: 0;
+          left: 0;
+          right: 0;
+          bottom: 0;
+          z-index: 9999;
+        "
+      >
+        <div
+          style="
+            font-size: 18px;
+            font-family: Helvetica, sans-serif;
+            line-height: 24px;
+            margin: 10%;
+            width: 80%;
+          "
+        >
+          <p>Oh no! It looks like JavaScript is not enabled in your browser.</p>
+          <p style="margin: 20px 0;">
+            <button
+              type="submit"
+              style="
+                background-color: #4630eb;
+                border-radius: 100px;
+                border: none;
+                box-shadow: none;
+                color: #fff;
+                cursor: pointer;
+                font-weight: bold;
+                line-height: 20px;
+                padding: 6px 16px;
+              "
+            >
+              Reload
+            </button>
+          </p>
+        </div>
+      </form>
+    </noscript>
+    <!-- The root element for your Expo app. -->
+    <div id="root"></div>
+  </body>
+</html>
diff --git a/web/webpack.config.js b/web/webpack.config.js
deleted file mode 100644
index d74cdd542..000000000
--- a/web/webpack.config.js
+++ /dev/null
@@ -1,121 +0,0 @@
-const path = require('path')
-const HtmlWebpackPlugin = require('html-webpack-plugin')
-const webpackEnv = process.env.NODE_ENV || 'development'
-
-const appDirectory = path.resolve(__dirname, '../')
-
-// NOTE: node modules that ship as typescript must be listed here
-const uncompiled_deps = [
-  '@bam.tech/react-native-image-resizer',
-  'react-native-fs',
-  'rn-fetch-blob',
-  'react-native-root-siblings',
-  'react-native-linear-gradient',
-]
-
-const babelLoaderConfiguration = {
-  test: /\.(js|jsx|ts|tsx)$/,
-  include: [
-    path.resolve(appDirectory, 'index.web.js'),
-    path.resolve(appDirectory, 'src'),
-    ...uncompiled_deps.map(dep =>
-      path.resolve(appDirectory, `node_modules/${dep}`),
-    ),
-  ],
-  use: {
-    loader: 'babel-loader',
-    options: {
-      cacheDirectory: true,
-      presets: ['module:metro-react-native-babel-preset'],
-      plugins: ['react-native-web'],
-    },
-  },
-}
-
-const imageLoaderConfiguration = {
-  test: /\.(gif|jpe?g|png|svg)$/,
-  use: {
-    loader: 'url-loader',
-    options: {
-      name: '[name].[ext]',
-      esModule: false,
-    },
-  },
-}
-
-const reactNativeWebWebviewConfiguration = {
-  test: /postMock.html$/,
-  use: {
-    loader: 'file-loader',
-    options: {
-      name: '[name].[ext]',
-    },
-  },
-}
-
-module.exports = {
-  mode: webpackEnv,
-
-  entry: [
-    // NOTE: load any web API polyfills needed here
-    path.resolve(appDirectory, 'index.web.js'),
-  ],
-
-  output: {
-    filename: 'bundle.web.js',
-    path: path.resolve(appDirectory, 'dist'),
-  },
-
-  plugins: [
-    new HtmlWebpackPlugin({
-      template: path.join(appDirectory, './public/index.html'),
-    }),
-  ],
-
-  module: {
-    rules: [
-      babelLoaderConfiguration,
-      imageLoaderConfiguration,
-      reactNativeWebWebviewConfiguration,
-    ],
-  },
-
-  devServer: {
-    historyApiFallback: {
-      rewrites: [
-        {from: /.*\/bundle.web.js/, to: '/bundle.web.js'},
-        {
-          from: /.*^\/(.*.hot-update.json)$/,
-          to: function (context) {
-            return '/' + context.parsedUrl.pathname.split('/').pop()
-          },
-        },
-        {
-          from: /.*^\/(.*.hot-update.js)$/,
-          to: function (context) {
-            return '/' + context.parsedUrl.pathname.split('/').pop()
-          },
-        },
-        {from: /.*/, to: '/index.html'},
-      ],
-    },
-  },
-
-  resolve: {
-    alias: {
-      'react-native$': 'react-native-web',
-      'react-native-linear-gradient': 'react-native-web-linear-gradient',
-      'react-native-webview': 'react-native-web-webview',
-    },
-    extensions: [
-      '.web.tsx',
-      '.web.ts',
-      '.tsx',
-      '.ts',
-      '.web.jsx',
-      '.web.js',
-      '.jsx',
-      '.js',
-    ],
-  },
-}
diff --git a/webpack.config.js b/webpack.config.js
new file mode 100644
index 000000000..28e5ca0db
--- /dev/null
+++ b/webpack.config.js
@@ -0,0 +1,26 @@
+const createExpoWebpackConfigAsync = require('@expo/webpack-config')
+const {withAlias} = require('@expo/webpack-config/addons')
+
+const reactNativeWebWebviewConfiguration = {
+  test: /postMock.html$/,
+  use: {
+    loader: 'file-loader',
+    options: {
+      name: '[name].[ext]',
+    },
+  },
+}
+
+module.exports = async function (env, argv) {
+  let config = await createExpoWebpackConfigAsync(env, argv)
+  config = withAlias(config, {
+    'react-native$': 'react-native-web',
+    'react-native-linear-gradient': 'react-native-web-linear-gradient',
+    'react-native-webview': 'react-native-web-webview',
+  })
+  config.module.rules = [
+    ...(config.module.rules || []),
+    reactNativeWebWebviewConfiguration,
+  ]
+  return config
+}
diff --git a/yarn.lock b/yarn.lock
index de326be4b..2c94091d5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -38,9 +38,9 @@
     uint8arrays "3.0.0"
 
 "@atproto/common@*":
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.1.0.tgz#4216a8fef5b985ab62ac21252a0f8ca0f4a0f210"
-  integrity sha512-OB5tWE2R19jwiMIs2IjQieH5KTUuMb98XGCn9h3xuu6NanwjlmbCYMv08fMYwIp3UQ6jcq//84cDT3Bu6fJD+A==
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.1.1.tgz#ec33a3b4995c91d3ad2e90fc4cdbc65284ceff84"
+  integrity sha512-GYwot5wF/z8iYGSPjrLHuratLc0CVgovmwfJss7+BUOB6y2/Vw8+1Vw0n9DDI0gb5vmx3UI8z0uJgC8aa8yuJg==
   dependencies:
     "@ipld/dag-cbor" "^7.0.3"
     multiformats "^9.6.4"
@@ -188,6 +188,13 @@
     "@atproto/lexicon" "*"
     zod "^3.14.2"
 
+"@babel/code-frame@7.10.4", "@babel/code-frame@~7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a"
+  integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==
+  dependencies:
+    "@babel/highlight" "^7.10.4"
+
 "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.18.6", "@babel/code-frame@^7.8.3":
   version "7.18.6"
   resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a"
@@ -444,7 +451,7 @@
     "@babel/traverse" "^7.21.0"
     "@babel/types" "^7.21.0"
 
-"@babel/highlight@^7.18.6":
+"@babel/highlight@^7.10.4", "@babel/highlight@^7.18.6":
   version "7.18.6"
   resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf"
   integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==
@@ -501,7 +508,7 @@
     "@babel/helper-plugin-utils" "^7.20.2"
     "@babel/plugin-syntax-class-static-block" "^7.14.5"
 
-"@babel/plugin-proposal-decorators@^7.16.4":
+"@babel/plugin-proposal-decorators@^7.12.9", "@babel/plugin-proposal-decorators@^7.16.4":
   version "7.21.0"
   resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.21.0.tgz#70e0c89fdcd7465c97593edb8f628ba6e4199d63"
   integrity sha512-MfgX49uRrFUTL/HvWtmx3zmpyzMMr4MTj3d527MLlr/4RTT9G/ytFFP7qet2uM2Ve03b+BkpWUpK+lRXnQ+v9w==
@@ -568,7 +575,7 @@
     "@babel/helper-plugin-utils" "^7.18.6"
     "@babel/plugin-syntax-numeric-separator" "^7.10.4"
 
-"@babel/plugin-proposal-object-rest-spread@^7.0.0", "@babel/plugin-proposal-object-rest-spread@^7.20.2":
+"@babel/plugin-proposal-object-rest-spread@^7.0.0", "@babel/plugin-proposal-object-rest-spread@^7.12.13", "@babel/plugin-proposal-object-rest-spread@^7.20.2":
   version "7.20.7"
   resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz#aa662940ef425779c75534a5c41e9d936edc390a"
   integrity sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==
@@ -1011,7 +1018,7 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.19.0"
 
-"@babel/plugin-transform-react-jsx@^7.0.0", "@babel/plugin-transform-react-jsx@^7.18.6":
+"@babel/plugin-transform-react-jsx@^7.0.0", "@babel/plugin-transform-react-jsx@^7.12.17", "@babel/plugin-transform-react-jsx@^7.18.6":
   version "7.21.0"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.21.0.tgz#656b42c2fdea0a6d8762075d58ef9d4e3c4ab8a2"
   integrity sha512-6OAWljMvQrZjR2DaNhVfRz6dkCAVV+ymcLUmaf8bccGOHn2v5rHJK3tTpij0BuhdYWP4LLaqj5lwcdlpAAPuvg==
@@ -1255,7 +1262,7 @@
   resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310"
   integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
 
-"@babel/runtime@^7.0.0", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.18.6", "@babel/runtime@^7.20.0", "@babel/runtime@^7.20.7", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4":
+"@babel/runtime@^7.0.0", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.16.3", "@babel/runtime@^7.18.6", "@babel/runtime@^7.20.0", "@babel/runtime@^7.20.7", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4":
   version "7.21.0"
   resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673"
   integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==
@@ -1449,6 +1456,431 @@
   resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.35.0.tgz#b7569632b0b788a0ca0e438235154e45d42813a7"
   integrity sha512-JXdzbRiWclLVoD8sNUjR443VVlYqiYmDVT6rGUEIEHU5YJW0gaVZwV2xgM7D4arkvASqD0IlLUVjHiFuxaftRw==
 
+"@expo/bunyan@4.0.0", "@expo/bunyan@^4.0.0":
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/@expo/bunyan/-/bunyan-4.0.0.tgz#be0c1de943c7987a9fbd309ea0b1acd605890c7b"
+  integrity sha512-Ydf4LidRB/EBI+YrB+cVLqIseiRfjUI/AeHBgjGMtq3GroraDu81OV7zqophRgupngoL3iS3JUMDMnxO7g39qA==
+  dependencies:
+    uuid "^8.0.0"
+  optionalDependencies:
+    mv "~2"
+    safe-json-stringify "~1"
+
+"@expo/cli@0.6.2":
+  version "0.6.2"
+  resolved "https://registry.yarnpkg.com/@expo/cli/-/cli-0.6.2.tgz#1090c9d23f49d9603c4c85fa85b878b2848da322"
+  integrity sha512-uhmrXNemXTbCTKP/ycyJHOU/KLGdFwVCrWNBzz1VkwnmL8yJV5F3C18a83ybFFnUNfkGHeH5LtID7CSNbbTWKg==
+  dependencies:
+    "@babel/runtime" "^7.20.0"
+    "@expo/code-signing-certificates" "0.0.5"
+    "@expo/config" "~8.0.0"
+    "@expo/config-plugins" "~6.0.0"
+    "@expo/dev-server" "0.2.3"
+    "@expo/devcert" "^1.0.0"
+    "@expo/json-file" "^8.2.37"
+    "@expo/metro-config" "~0.7.0"
+    "@expo/osascript" "^2.0.31"
+    "@expo/package-manager" "~1.0.0"
+    "@expo/plist" "^0.0.20"
+    "@expo/prebuild-config" "6.0.0"
+    "@expo/rudder-sdk-node" "1.1.1"
+    "@expo/spawn-async" "1.5.0"
+    "@expo/xcpretty" "^4.2.1"
+    "@urql/core" "2.3.6"
+    "@urql/exchange-retry" "0.3.0"
+    accepts "^1.3.8"
+    arg "4.1.0"
+    better-opn "~3.0.2"
+    bplist-parser "^0.3.1"
+    cacache "^15.3.0"
+    chalk "^4.0.0"
+    ci-info "^3.3.0"
+    debug "^4.3.4"
+    env-editor "^0.4.1"
+    form-data "^3.0.1"
+    freeport-async "2.0.0"
+    fs-extra "~8.1.0"
+    getenv "^1.0.0"
+    graphql "15.8.0"
+    graphql-tag "^2.10.1"
+    https-proxy-agent "^5.0.1"
+    internal-ip "4.3.0"
+    is-root "^2.1.0"
+    js-yaml "^3.13.1"
+    json-schema-deref-sync "^0.13.0"
+    md5-file "^3.2.3"
+    md5hex "^1.0.0"
+    minipass "3.1.6"
+    node-fetch "^2.6.7"
+    node-forge "^1.3.1"
+    npm-package-arg "^7.0.0"
+    ora "3.4.0"
+    pretty-bytes "5.6.0"
+    progress "2.0.3"
+    prompts "^2.3.2"
+    qrcode-terminal "0.11.0"
+    requireg "^0.2.2"
+    resolve-from "^5.0.0"
+    semver "^6.3.0"
+    send "^0.18.0"
+    slugify "^1.3.4"
+    structured-headers "^0.4.1"
+    tar "^6.0.5"
+    tempy "^0.7.1"
+    terminal-link "^2.1.1"
+    text-table "^0.2.0"
+    url-join "4.0.0"
+    wrap-ansi "^7.0.0"
+
+"@expo/code-signing-certificates@0.0.5":
+  version "0.0.5"
+  resolved "https://registry.yarnpkg.com/@expo/code-signing-certificates/-/code-signing-certificates-0.0.5.tgz#a693ff684fb20c4725dade4b88a6a9f96b02496c"
+  integrity sha512-BNhXkY1bblxKZpltzAx98G2Egj9g1Q+JRcvR7E99DOj862FTCX+ZPsAUtPTr7aHxwtrL7+fL3r0JSmM9kBm+Bw==
+  dependencies:
+    node-forge "^1.2.1"
+    nullthrows "^1.1.1"
+
+"@expo/config-plugins@4.1.1":
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-4.1.1.tgz#ffb20b3d2be4e2509e8bf846721641dc072718c7"
+  integrity sha512-lo3tVxRhwM9jfxPHJcURsH5WvU26kX12h5EB3C7kjVhgdQPLkvT8Jk8Cx0KSL8MXKcry2xQvZ2uuwWLkMeplJw==
+  dependencies:
+    "@expo/config-types" "^44.0.0"
+    "@expo/json-file" "8.2.35"
+    "@expo/plist" "0.0.18"
+    "@expo/sdk-runtime-versions" "^1.0.0"
+    "@react-native/normalize-color" "^2.0.0"
+    chalk "^4.1.2"
+    debug "^4.3.1"
+    find-up "~5.0.0"
+    getenv "^1.0.0"
+    glob "7.1.6"
+    resolve-from "^5.0.0"
+    semver "^7.3.5"
+    slash "^3.0.0"
+    xcode "^3.0.1"
+    xml2js "0.4.23"
+
+"@expo/config-plugins@6.0.1", "@expo/config-plugins@~6.0.0":
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-6.0.1.tgz#827cb34c51f725d8825b0768df6550c1cf81d457"
+  integrity sha512-6mqZutxeibXFeqFfoZApFUEH2n1RxGXYMHCdJrDj4eXDBBFZ3aJ0XBoroZcHHHvfRieEsf54vNyJoWp7JZGj8g==
+  dependencies:
+    "@expo/config-types" "^48.0.0"
+    "@expo/json-file" "~8.2.37"
+    "@expo/plist" "^0.0.20"
+    "@expo/sdk-runtime-versions" "^1.0.0"
+    "@react-native/normalize-color" "^2.0.0"
+    chalk "^4.1.2"
+    debug "^4.3.1"
+    find-up "~5.0.0"
+    getenv "^1.0.0"
+    glob "7.1.6"
+    resolve-from "^5.0.0"
+    semver "^7.3.5"
+    slash "^3.0.0"
+    xcode "^3.0.1"
+    xml2js "0.4.23"
+
+"@expo/config-types@^44.0.0":
+  version "44.0.0"
+  resolved "https://registry.yarnpkg.com/@expo/config-types/-/config-types-44.0.0.tgz#d3480fe2c99f9e895dae4ebba58b74ed72d03e26"
+  integrity sha512-d+gpdKOAhqaD5RmcMzGgKzNtvE1w+GCqpFQNSXLliYlXjj+Tv0eL8EPeAdPtvke0vowpPFwd5McXLA90dgY6Jg==
+
+"@expo/config-types@^48.0.0":
+  version "48.0.0"
+  resolved "https://registry.yarnpkg.com/@expo/config-types/-/config-types-48.0.0.tgz#15a46921565ffeda3c3ba010701398f05193d5b3"
+  integrity sha512-DwyV4jTy/+cLzXGAo1xftS6mVlSiLIWZjl9DjTCLPFVgNYQxnh7htPilRv4rBhiNs7KaznWqKU70+4zQoKVT9A==
+
+"@expo/config@6.0.20":
+  version "6.0.20"
+  resolved "https://registry.yarnpkg.com/@expo/config/-/config-6.0.20.tgz#fb3b9fbcaf97f678714fe05603ba5d3aacfa0a25"
+  integrity sha512-m2T1/hB4TyLkQElOUwOajn/7gBcPaGyfVwoVsuJMEh0yrNvNFtXP+nl87Cm53g5q+VyfwJUgbewPQ3j/UXkI6Q==
+  dependencies:
+    "@babel/code-frame" "~7.10.4"
+    "@expo/config-plugins" "4.1.1"
+    "@expo/config-types" "^44.0.0"
+    "@expo/json-file" "8.2.35"
+    getenv "^1.0.0"
+    glob "7.1.6"
+    require-from-string "^2.0.2"
+    resolve-from "^5.0.0"
+    semver "7.3.2"
+    slugify "^1.3.4"
+    sucrase "^3.20.0"
+
+"@expo/config@8.0.2", "@expo/config@~8.0.0":
+  version "8.0.2"
+  resolved "https://registry.yarnpkg.com/@expo/config/-/config-8.0.2.tgz#53ecfa9bafc97b990ff9e34e210205b0e3f05751"
+  integrity sha512-WubrzTNNdAXy1FU8TdyQ7D9YtDj2tN3fWXDq+C8In+nB7Qc08zwH9cVdaGZ+rBVmjFZBh5ACfObKq/m9cm4QQA==
+  dependencies:
+    "@babel/code-frame" "~7.10.4"
+    "@expo/config-plugins" "~6.0.0"
+    "@expo/config-types" "^48.0.0"
+    "@expo/json-file" "^8.2.37"
+    getenv "^1.0.0"
+    glob "7.1.6"
+    require-from-string "^2.0.2"
+    resolve-from "^5.0.0"
+    semver "7.3.2"
+    slugify "^1.3.4"
+    sucrase "^3.20.0"
+
+"@expo/configure-splash-screen@^0.6.0":
+  version "0.6.0"
+  resolved "https://registry.yarnpkg.com/@expo/configure-splash-screen/-/configure-splash-screen-0.6.0.tgz#07d97ee512fd859fcc09506ba3762fd6263ebc39"
+  integrity sha512-4DyPoNXJqx9bN4nEwF3HQreo//ECu7gDe1Xor3dnnzFm9P/VDxAKdbEhA0n+R6fgkNfT2onVHWijqvdpTS3Xew==
+  dependencies:
+    color-string "^1.5.3"
+    commander "^5.1.0"
+    fs-extra "^9.0.0"
+    glob "^7.1.6"
+    lodash "^4.17.15"
+    pngjs "^5.0.0"
+    xcode "^3.0.0"
+    xml-js "^1.6.11"
+
+"@expo/dev-server@0.2.3":
+  version "0.2.3"
+  resolved "https://registry.yarnpkg.com/@expo/dev-server/-/dev-server-0.2.3.tgz#736317cc1340b28dc49da8a45b85040306048e24"
+  integrity sha512-9+6QGRdymj3dmTp1vUpROvWJ+Ezz6Qp9xHafAcaRHzw322pUCOiRKxTYqDqYYZ/72shrHPGQ2CiIXTnV1vM2tA==
+  dependencies:
+    "@expo/bunyan" "4.0.0"
+    "@expo/metro-config" "~0.7.0"
+    "@expo/osascript" "2.0.33"
+    "@expo/spawn-async" "^1.5.0"
+    body-parser "^1.20.1"
+    chalk "^4.0.0"
+    connect "^3.7.0"
+    fs-extra "9.0.0"
+    is-docker "^2.0.0"
+    is-wsl "^2.1.1"
+    node-fetch "^2.6.0"
+    open "^8.3.0"
+    resolve-from "^5.0.0"
+    semver "7.3.2"
+    serialize-error "6.0.0"
+    temp-dir "^2.0.0"
+
+"@expo/devcert@^1.0.0":
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/@expo/devcert/-/devcert-1.1.0.tgz#d148eb9180db6753c438192e73a123fb13b662ac"
+  integrity sha512-ghUVhNJQOCTdQckSGTHctNp/0jzvVoMMkVh+6SHn+TZj8sU15U/npXIDt8NtQp0HedlPaCgkVdMu8Sacne0aEA==
+  dependencies:
+    application-config-path "^0.1.0"
+    command-exists "^1.2.4"
+    debug "^3.1.0"
+    eol "^0.9.1"
+    get-port "^3.2.0"
+    glob "^7.1.2"
+    lodash "^4.17.4"
+    mkdirp "^0.5.1"
+    password-prompt "^1.0.4"
+    rimraf "^2.6.2"
+    sudo-prompt "^8.2.0"
+    tmp "^0.0.33"
+    tslib "^2.4.0"
+
+"@expo/image-utils@0.3.22":
+  version "0.3.22"
+  resolved "https://registry.yarnpkg.com/@expo/image-utils/-/image-utils-0.3.22.tgz#3a45fb2e268d20fcc761c87bca3aca7fd8e24260"
+  integrity sha512-uzq+RERAtkWypOFOLssFnXXqEqKjNj9eXN7e97d/EXUAojNcLDoXc0sL+F5B1I4qtlsnhX01kcpoIBBZD8wZNQ==
+  dependencies:
+    "@expo/spawn-async" "1.5.0"
+    chalk "^4.0.0"
+    fs-extra "9.0.0"
+    getenv "^1.0.0"
+    jimp-compact "0.16.1"
+    mime "^2.4.4"
+    node-fetch "^2.6.0"
+    parse-png "^2.1.0"
+    resolve-from "^5.0.0"
+    semver "7.3.2"
+    tempy "0.3.0"
+
+"@expo/image-utils@0.3.23":
+  version "0.3.23"
+  resolved "https://registry.yarnpkg.com/@expo/image-utils/-/image-utils-0.3.23.tgz#f14fd7e1f5ff6f8e4911a41e27dd274470665c3f"
+  integrity sha512-nhUVvW0TrRE4jtWzHQl8TR4ox7kcmrc2I0itaeJGjxF5A54uk7avgA0wRt7jP1rdvqQo1Ke1lXyLYREdhN9tPw==
+  dependencies:
+    "@expo/spawn-async" "1.5.0"
+    chalk "^4.0.0"
+    fs-extra "9.0.0"
+    getenv "^1.0.0"
+    jimp-compact "0.16.1"
+    mime "^2.4.4"
+    node-fetch "^2.6.0"
+    parse-png "^2.1.0"
+    resolve-from "^5.0.0"
+    semver "7.3.2"
+    tempy "0.3.0"
+
+"@expo/json-file@8.2.35":
+  version "8.2.35"
+  resolved "https://registry.yarnpkg.com/@expo/json-file/-/json-file-8.2.35.tgz#d0f0c74cdde2ae26686708912cf23ff81a8d67ac"
+  integrity sha512-cQFLGSNRRFbN9EIhVDpMCYuzXbrHUOmKEqitBR+nrU6surjKGsOsN9Ubyn/L/LAGlFvT293E4XY5zsOtJyiPZQ==
+  dependencies:
+    "@babel/code-frame" "~7.10.4"
+    json5 "^1.0.1"
+    write-file-atomic "^2.3.0"
+
+"@expo/json-file@^8.2.37", "@expo/json-file@~8.2.37":
+  version "8.2.37"
+  resolved "https://registry.yarnpkg.com/@expo/json-file/-/json-file-8.2.37.tgz#9c02d3b42134907c69cc0a027b18671b69344049"
+  integrity sha512-YaH6rVg11JoTS2P6LsW7ybS2CULjf40AbnAHw2F1eDPuheprNjARZMnyHFPkKv7GuxCy+B9GPcbOKgc4cgA80Q==
+  dependencies:
+    "@babel/code-frame" "~7.10.4"
+    json5 "^2.2.2"
+    write-file-atomic "^2.3.0"
+
+"@expo/metro-config@~0.7.0":
+  version "0.7.1"
+  resolved "https://registry.yarnpkg.com/@expo/metro-config/-/metro-config-0.7.1.tgz#eaae792da23554c1abbc401df868566fab29951b"
+  integrity sha512-vGWU62Zp5pRGw5IEHDNdqvsy62/hu/Na7bswePYVjoaItOjJY7+qilFeF0AAK+3V8qAM8fpltH3ByylKfWaA7A==
+  dependencies:
+    "@expo/config" "~8.0.0"
+    chalk "^4.1.0"
+    debug "^4.3.2"
+    find-yarn-workspace-root "~2.0.0"
+    getenv "^1.0.0"
+    resolve-from "^5.0.0"
+    sucrase "^3.20.0"
+
+"@expo/osascript@2.0.33", "@expo/osascript@^2.0.31":
+  version "2.0.33"
+  resolved "https://registry.yarnpkg.com/@expo/osascript/-/osascript-2.0.33.tgz#e9dcc8da54466c11939074aa71a006024ea884b1"
+  integrity sha512-FQinlwHrTlJbntp8a7NAlCKedVXe06Va/0DSLXRO8lZVtgbEMrYYSUZWQNcOlNtc58c2elNph6z9dMOYwSo3JQ==
+  dependencies:
+    "@expo/spawn-async" "^1.5.0"
+    exec-async "^2.2.0"
+
+"@expo/package-manager@~1.0.0":
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/@expo/package-manager/-/package-manager-1.0.1.tgz#d0d6b0937df5016b0155b1d87bbaba9839bbeb9f"
+  integrity sha512-ue6NIIsNafa2bK7zUl7Y61YNtkPsg7sJcTOyQo/87Yqf6Q+2bOrvdw1xjviaFrMsTZcpOPVf+ZIEYtE0lw0k6A==
+  dependencies:
+    "@expo/json-file" "^8.2.37"
+    "@expo/spawn-async" "^1.5.0"
+    ansi-regex "^5.0.0"
+    chalk "^4.0.0"
+    find-up "^5.0.0"
+    find-yarn-workspace-root "~2.0.0"
+    js-yaml "^3.13.1"
+    micromatch "^4.0.2"
+    npm-package-arg "^7.0.0"
+    split "^1.0.1"
+    sudo-prompt "9.1.1"
+
+"@expo/plist@0.0.18":
+  version "0.0.18"
+  resolved "https://registry.yarnpkg.com/@expo/plist/-/plist-0.0.18.tgz#9abcde78df703a88f6d9fa1a557ee2f045d178b0"
+  integrity sha512-+48gRqUiz65R21CZ/IXa7RNBXgAI/uPSdvJqoN9x1hfL44DNbUoWHgHiEXTx7XelcATpDwNTz6sHLfy0iNqf+w==
+  dependencies:
+    "@xmldom/xmldom" "~0.7.0"
+    base64-js "^1.2.3"
+    xmlbuilder "^14.0.0"
+
+"@expo/plist@^0.0.20":
+  version "0.0.20"
+  resolved "https://registry.yarnpkg.com/@expo/plist/-/plist-0.0.20.tgz#a6b3124438031c02b762bad5a47b70584d3c0072"
+  integrity sha512-UXQ4LXCfTZ580LDHGJ5q62jSTwJFFJ1GqBu8duQMThiHKWbMJ+gajJh6rsB6EJ3aLUr9wcauxneL5LVRFxwBEA==
+  dependencies:
+    "@xmldom/xmldom" "~0.7.7"
+    base64-js "^1.2.3"
+    xmlbuilder "^14.0.0"
+
+"@expo/prebuild-config@6.0.0":
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/@expo/prebuild-config/-/prebuild-config-6.0.0.tgz#c8e7f634f3ecf2272673f371c47d5d22950129a4"
+  integrity sha512-UW0QKAoRelsalVMhAG1tmegwS+2tbefvUi6/0QiKPlMLg8GFDQ5ZnzsSmuljD0SzT5yGg8oSpKYhnrXJ6pRmIQ==
+  dependencies:
+    "@expo/config" "~8.0.0"
+    "@expo/config-plugins" "~6.0.0"
+    "@expo/config-types" "^48.0.0"
+    "@expo/image-utils" "0.3.22"
+    "@expo/json-file" "^8.2.37"
+    debug "^4.3.1"
+    fs-extra "^9.0.0"
+    resolve-from "^5.0.0"
+    semver "7.3.2"
+    xml2js "0.4.23"
+
+"@expo/rudder-sdk-node@1.1.1":
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/@expo/rudder-sdk-node/-/rudder-sdk-node-1.1.1.tgz#6aa575f346833eb6290282118766d4919c808c6a"
+  integrity sha512-uy/hS/awclDJ1S88w9UGpc6Nm9XnNUjzOAAib1A3PVAnGQIwebg8DpFqOthFBTlZxeuV/BKbZ5jmTbtNZkp1WQ==
+  dependencies:
+    "@expo/bunyan" "^4.0.0"
+    "@segment/loosely-validate-event" "^2.0.0"
+    fetch-retry "^4.1.1"
+    md5 "^2.2.1"
+    node-fetch "^2.6.1"
+    remove-trailing-slash "^0.1.0"
+    uuid "^8.3.2"
+
+"@expo/sdk-runtime-versions@^1.0.0":
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/@expo/sdk-runtime-versions/-/sdk-runtime-versions-1.0.0.tgz#d7ebd21b19f1c6b0395e50d78da4416941c57f7c"
+  integrity sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ==
+
+"@expo/spawn-async@1.5.0":
+  version "1.5.0"
+  resolved "https://registry.yarnpkg.com/@expo/spawn-async/-/spawn-async-1.5.0.tgz#799827edd8c10ef07eb1a2ff9dcfe081d596a395"
+  integrity sha512-LB7jWkqrHo+5fJHNrLAFdimuSXQ2MQ4lA7SQW5bf/HbsXuV2VrT/jN/M8f/KoWt0uJMGN4k/j7Opx4AvOOxSew==
+  dependencies:
+    cross-spawn "^6.0.5"
+
+"@expo/spawn-async@^1.5.0":
+  version "1.7.0"
+  resolved "https://registry.yarnpkg.com/@expo/spawn-async/-/spawn-async-1.7.0.tgz#3ab6082b24318cccc4e73b13464da91325555500"
+  integrity sha512-sqPAjOEFTrjaTybrh9SnPFLInDXcoMC06psEFmH68jLTmoipSQCq8GCEfIoHhxRDALWB+DsiwXJSbXlE/iVIIQ==
+  dependencies:
+    cross-spawn "^7.0.3"
+
+"@expo/vector-icons@^13.0.0":
+  version "13.0.0"
+  resolved "https://registry.yarnpkg.com/@expo/vector-icons/-/vector-icons-13.0.0.tgz#e2989b85e95a82bce216f88cf8fb583ab050ec95"
+  integrity sha512-TI+l71+5aSKnShYclFa14Kum+hQMZ86b95SH6tQUG3qZEmLTarvWpKwqtTwQKqvlJSJrpFiSFu3eCuZokY6zWA==
+
+"@expo/webpack-config@^18.0.1":
+  version "18.0.1"
+  resolved "https://registry.yarnpkg.com/@expo/webpack-config/-/webpack-config-18.0.1.tgz#e657ae4490052a9ada6bf703cfd721324a5be741"
+  integrity sha512-0C+wjmmQ0usySdhtzeRp0yYuf9zkUZ/kNgA6AHQ9N7eG4JIr0DM1c87g119smxcJTbd8N+//mv5znPxSJqBqmg==
+  dependencies:
+    "@babel/core" "^7.16.0"
+    "@expo/config" "6.0.20"
+    babel-loader "^8.2.3"
+    chalk "^4.0.0"
+    clean-webpack-plugin "^4.0.0"
+    copy-webpack-plugin "^10.2.0"
+    css-loader "^6.5.1"
+    css-minimizer-webpack-plugin "^3.4.1"
+    expo-pwa "0.0.124"
+    find-up "^5.0.0"
+    find-yarn-workspace-root "~2.0.0"
+    getenv "^1.0.0"
+    html-webpack-plugin "^5.5.0"
+    is-wsl "^2.0.0"
+    mini-css-extract-plugin "^2.5.2"
+    node-html-parser "^5.2.0"
+    semver "~7.3.2"
+    source-map-loader "^3.0.1"
+    style-loader "^3.3.1"
+    terser-webpack-plugin "^5.3.0"
+    webpack "^5.64.4"
+    webpack-dev-server "^4.11.1"
+    webpack-manifest-plugin "^4.1.1"
+
+"@expo/xcpretty@^4.2.1":
+  version "4.2.2"
+  resolved "https://registry.yarnpkg.com/@expo/xcpretty/-/xcpretty-4.2.2.tgz#7890f86b017015be8a20242ae74fe6ed4b80a92c"
+  integrity sha512-Lke/geldJqUV0Dfxg5/QIOugOzdqZ/rQ9yHKSgGbjZtG1uiSqWyFwWvXmrdd3/sIdX33eykGvIcf+OrvvcXVUw==
+  dependencies:
+    "@babel/code-frame" "7.10.4"
+    chalk "^4.1.0"
+    find-up "^5.0.0"
+    js-yaml "^4.1.0"
+
 "@fortawesome/fontawesome-common-types@6.3.0":
   version "6.3.0"
   resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.3.0.tgz#51f734e64511dbc3674cd347044d02f4dd26e86b"
@@ -1483,6 +1915,11 @@
     humps "^2.0.1"
     prop-types "^15.7.2"
 
+"@gar/promisify@^1.0.1":
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6"
+  integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==
+
 "@gorhom/bottom-sheet@^4":
   version "4.4.5"
   resolved "https://registry.yarnpkg.com/@gorhom/bottom-sheet/-/bottom-sheet-4.4.5.tgz#b9041b01ce1af9a936e7c0fc1d78f026d759eebe"
@@ -1498,6 +1935,11 @@
   dependencies:
     nanoid "^3.3.1"
 
+"@graphql-typed-document-node/core@^3.1.0":
+  version "3.1.2"
+  resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.1.2.tgz#6fc464307cbe3c8ca5064549b806360d84457b04"
+  integrity sha512-9anpBMM9mEgZN4wr2v8wHJI2/u5TnnggewRN6OlvXTTnuVyoY19X6rOv9XTqKRw6dcGKwZsBi8n0kDE2I5i4VA==
+
 "@hapi/hoek@^10.0.0":
   version "10.0.1"
   resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-10.0.1.tgz#ee9da297fabc557e1c040a0f44ee89c266ccc306"
@@ -1591,16 +2033,16 @@
     jest-util "^28.1.3"
     slash "^3.0.0"
 
-"@jest/console@^29.4.3":
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.4.3.tgz#1f25a99f7f860e4c46423b5b1038262466fadde1"
-  integrity sha512-W/o/34+wQuXlgqlPYTansOSiBnuxrTv61dEVkA6HNmpcgHLUjfaUbdqt6oVvOzaawwo9IdW9QOtMgQ1ScSZC4A==
+"@jest/console@^29.5.0":
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.5.0.tgz#593a6c5c0d3f75689835f1b3b4688c4f8544cb57"
+  integrity sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ==
   dependencies:
-    "@jest/types" "^29.4.3"
+    "@jest/types" "^29.5.0"
     "@types/node" "*"
     chalk "^4.0.0"
-    jest-message-util "^29.4.3"
-    jest-util "^29.4.3"
+    jest-message-util "^29.5.0"
+    jest-util "^29.5.0"
     slash "^3.0.0"
 
 "@jest/core@^27.5.1":
@@ -1637,46 +2079,46 @@
     slash "^3.0.0"
     strip-ansi "^6.0.0"
 
-"@jest/core@^29.4.3":
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.4.3.tgz#829dd65bffdb490de5b0f69e97de8e3b5eadd94b"
-  integrity sha512-56QvBq60fS4SPZCuM7T+7scNrkGIe7Mr6PVIXUpu48ouvRaWOFqRPV91eifvFM0ay2HmfswXiGf97NGUN5KofQ==
-  dependencies:
-    "@jest/console" "^29.4.3"
-    "@jest/reporters" "^29.4.3"
-    "@jest/test-result" "^29.4.3"
-    "@jest/transform" "^29.4.3"
-    "@jest/types" "^29.4.3"
+"@jest/core@^29.5.0":
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.5.0.tgz#76674b96904484e8214614d17261cc491e5f1f03"
+  integrity sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==
+  dependencies:
+    "@jest/console" "^29.5.0"
+    "@jest/reporters" "^29.5.0"
+    "@jest/test-result" "^29.5.0"
+    "@jest/transform" "^29.5.0"
+    "@jest/types" "^29.5.0"
     "@types/node" "*"
     ansi-escapes "^4.2.1"
     chalk "^4.0.0"
     ci-info "^3.2.0"
     exit "^0.1.2"
     graceful-fs "^4.2.9"
-    jest-changed-files "^29.4.3"
-    jest-config "^29.4.3"
-    jest-haste-map "^29.4.3"
-    jest-message-util "^29.4.3"
+    jest-changed-files "^29.5.0"
+    jest-config "^29.5.0"
+    jest-haste-map "^29.5.0"
+    jest-message-util "^29.5.0"
     jest-regex-util "^29.4.3"
-    jest-resolve "^29.4.3"
-    jest-resolve-dependencies "^29.4.3"
-    jest-runner "^29.4.3"
-    jest-runtime "^29.4.3"
-    jest-snapshot "^29.4.3"
-    jest-util "^29.4.3"
-    jest-validate "^29.4.3"
-    jest-watcher "^29.4.3"
+    jest-resolve "^29.5.0"
+    jest-resolve-dependencies "^29.5.0"
+    jest-runner "^29.5.0"
+    jest-runtime "^29.5.0"
+    jest-snapshot "^29.5.0"
+    jest-util "^29.5.0"
+    jest-validate "^29.5.0"
+    jest-watcher "^29.5.0"
     micromatch "^4.0.4"
-    pretty-format "^29.4.3"
+    pretty-format "^29.5.0"
     slash "^3.0.0"
     strip-ansi "^6.0.0"
 
 "@jest/create-cache-key-function@^29.2.1":
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/@jest/create-cache-key-function/-/create-cache-key-function-29.4.3.tgz#ea37769f69523019d81ee089a25a62550f209eb7"
-  integrity sha512-AJVFQTTy6jnZAQiAZrdOaTAPzJUrvAE/4IMe+Foav6WPhypFszqg7a4lOTyuzYQEEiT5RSrGYg9IY+/ivxiyXw==
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/@jest/create-cache-key-function/-/create-cache-key-function-29.5.0.tgz#24e019d03e634be4affe8bcee787d75a36ae57a2"
+  integrity sha512-LIDZyZgnZss7uikvBKBB/USWwG+GO8+GnwRWT+YkCGDGsqLQlhm9BC3z6+7+eMs1kUlvXQIWEzBR8Q2Pnvx6lg==
   dependencies:
-    "@jest/types" "^29.4.3"
+    "@jest/types" "^29.5.0"
 
 "@jest/environment@^27.5.1":
   version "27.5.1"
@@ -1688,30 +2130,30 @@
     "@types/node" "*"
     jest-mock "^27.5.1"
 
-"@jest/environment@^29.4.3":
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.4.3.tgz#9fe2f3169c3b33815dc4bd3960a064a83eba6548"
-  integrity sha512-dq5S6408IxIa+lr54zeqce+QgI+CJT4nmmA+1yzFgtcsGK8c/EyiUb9XQOgz3BMKrRDfKseeOaxj2eO8LlD3lA==
+"@jest/environment@^29.5.0":
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.5.0.tgz#9152d56317c1fdb1af389c46640ba74ef0bb4c65"
+  integrity sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ==
   dependencies:
-    "@jest/fake-timers" "^29.4.3"
-    "@jest/types" "^29.4.3"
+    "@jest/fake-timers" "^29.5.0"
+    "@jest/types" "^29.5.0"
     "@types/node" "*"
-    jest-mock "^29.4.3"
+    jest-mock "^29.5.0"
 
-"@jest/expect-utils@^29.4.3":
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.4.3.tgz#95ce4df62952f071bcd618225ac7c47eaa81431e"
-  integrity sha512-/6JWbkxHOP8EoS8jeeTd9dTfc9Uawi+43oLKHfp6zzux3U2hqOOVnV3ai4RpDYHOccL6g+5nrxpoc8DmJxtXVQ==
+"@jest/expect-utils@^29.5.0":
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.5.0.tgz#f74fad6b6e20f924582dc8ecbf2cb800fe43a036"
+  integrity sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg==
   dependencies:
     jest-get-type "^29.4.3"
 
-"@jest/expect@^29.4.3":
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.4.3.tgz#d31a28492e45a6bcd0f204a81f783fe717045c6e"
-  integrity sha512-iktRU/YsxEtumI9zsPctYUk7ptpC+AVLLk1Ax3AsA4g1C+8OOnKDkIQBDHtD5hA/+VtgMd5AWI5gNlcAlt2vxQ==
+"@jest/expect@^29.5.0":
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.5.0.tgz#80952f5316b23c483fbca4363ce822af79c38fba"
+  integrity sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g==
   dependencies:
-    expect "^29.4.3"
-    jest-snapshot "^29.4.3"
+    expect "^29.5.0"
+    jest-snapshot "^29.5.0"
 
 "@jest/fake-timers@^27.5.1":
   version "27.5.1"
@@ -1725,17 +2167,17 @@
     jest-mock "^27.5.1"
     jest-util "^27.5.1"
 
-"@jest/fake-timers@^29.4.3":
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.4.3.tgz#31e982638c60fa657d310d4b9d24e023064027b0"
-  integrity sha512-4Hote2MGcCTWSD2gwl0dwbCpBRHhE6olYEuTj8FMowdg3oQWNKr2YuxenPQYZ7+PfqPY1k98wKDU4Z+Hvd4Tiw==
+"@jest/fake-timers@^29.5.0":
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.5.0.tgz#d4d09ec3286b3d90c60bdcd66ed28d35f1b4dc2c"
+  integrity sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg==
   dependencies:
-    "@jest/types" "^29.4.3"
+    "@jest/types" "^29.5.0"
     "@sinonjs/fake-timers" "^10.0.2"
     "@types/node" "*"
-    jest-message-util "^29.4.3"
-    jest-mock "^29.4.3"
-    jest-util "^29.4.3"
+    jest-message-util "^29.5.0"
+    jest-mock "^29.5.0"
+    jest-util "^29.5.0"
 
 "@jest/globals@^27.5.1":
   version "27.5.1"
@@ -1746,15 +2188,15 @@
     "@jest/types" "^27.5.1"
     expect "^27.5.1"
 
-"@jest/globals@^29.4.3":
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.4.3.tgz#63a2c4200d11bc6d46f12bbe25b07f771fce9279"
-  integrity sha512-8BQ/5EzfOLG7AaMcDh7yFCbfRLtsc+09E1RQmRBI4D6QQk4m6NSK/MXo+3bJrBN0yU8A2/VIcqhvsOLFmziioA==
+"@jest/globals@^29.5.0":
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.5.0.tgz#6166c0bfc374c58268677539d0c181f9c1833298"
+  integrity sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ==
   dependencies:
-    "@jest/environment" "^29.4.3"
-    "@jest/expect" "^29.4.3"
-    "@jest/types" "^29.4.3"
-    jest-mock "^29.4.3"
+    "@jest/environment" "^29.5.0"
+    "@jest/expect" "^29.5.0"
+    "@jest/types" "^29.5.0"
+    jest-mock "^29.5.0"
 
 "@jest/reporters@^27.5.1":
   version "27.5.1"
@@ -1787,16 +2229,16 @@
     terminal-link "^2.0.0"
     v8-to-istanbul "^8.1.0"
 
-"@jest/reporters@^29.4.3":
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.4.3.tgz#0a68a0c0f20554760cc2e5443177a0018969e353"
-  integrity sha512-sr2I7BmOjJhyqj9ANC6CTLsL4emMoka7HkQpcoMRlhCbQJjz2zsRzw0BDPiPyEFDXAbxKgGFYuQZiSJ1Y6YoTg==
+"@jest/reporters@^29.5.0":
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.5.0.tgz#985dfd91290cd78ddae4914ba7921bcbabe8ac9b"
+  integrity sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA==
   dependencies:
     "@bcoe/v8-coverage" "^0.2.3"
-    "@jest/console" "^29.4.3"
-    "@jest/test-result" "^29.4.3"
-    "@jest/transform" "^29.4.3"
-    "@jest/types" "^29.4.3"
+    "@jest/console" "^29.5.0"
+    "@jest/test-result" "^29.5.0"
+    "@jest/transform" "^29.5.0"
+    "@jest/types" "^29.5.0"
     "@jridgewell/trace-mapping" "^0.3.15"
     "@types/node" "*"
     chalk "^4.0.0"
@@ -1809,9 +2251,9 @@
     istanbul-lib-report "^3.0.0"
     istanbul-lib-source-maps "^4.0.0"
     istanbul-reports "^3.1.3"
-    jest-message-util "^29.4.3"
-    jest-util "^29.4.3"
-    jest-worker "^29.4.3"
+    jest-message-util "^29.5.0"
+    jest-util "^29.5.0"
+    jest-worker "^29.5.0"
     slash "^3.0.0"
     string-length "^4.0.1"
     strip-ansi "^6.0.0"
@@ -1869,13 +2311,13 @@
     "@types/istanbul-lib-coverage" "^2.0.0"
     collect-v8-coverage "^1.0.0"
 
-"@jest/test-result@^29.4.3":
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.4.3.tgz#e13d973d16c8c7cc0c597082d5f3b9e7f796ccb8"
-  integrity sha512-Oi4u9NfBolMq9MASPwuWTlC5WvmNRwI4S8YrQg5R5Gi47DYlBe3sh7ILTqi/LGrK1XUE4XY9KZcQJTH1WJCLLA==
+"@jest/test-result@^29.5.0":
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.5.0.tgz#7c856a6ca84f45cc36926a4e9c6b57f1973f1408"
+  integrity sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ==
   dependencies:
-    "@jest/console" "^29.4.3"
-    "@jest/types" "^29.4.3"
+    "@jest/console" "^29.5.0"
+    "@jest/types" "^29.5.0"
     "@types/istanbul-lib-coverage" "^2.0.0"
     collect-v8-coverage "^1.0.0"
 
@@ -1889,14 +2331,14 @@
     jest-haste-map "^27.5.1"
     jest-runtime "^27.5.1"
 
-"@jest/test-sequencer@^29.4.3":
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.4.3.tgz#0862e876a22993385a0f3e7ea1cc126f208a2898"
-  integrity sha512-yi/t2nES4GB4G0mjLc0RInCq/cNr9dNwJxcGg8sslajua5Kb4kmozAc+qPLzplhBgfw1vLItbjyHzUN92UXicw==
+"@jest/test-sequencer@^29.5.0":
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.5.0.tgz#34d7d82d3081abd523dbddc038a3ddcb9f6d3cc4"
+  integrity sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ==
   dependencies:
-    "@jest/test-result" "^29.4.3"
+    "@jest/test-result" "^29.5.0"
     graceful-fs "^4.2.9"
-    jest-haste-map "^29.4.3"
+    jest-haste-map "^29.5.0"
     slash "^3.0.0"
 
 "@jest/transform@^27.5.1":
@@ -1920,22 +2362,22 @@
     source-map "^0.6.1"
     write-file-atomic "^3.0.0"
 
-"@jest/transform@^29.4.3":
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.4.3.tgz#f7d17eac9cb5bb2e1222ea199c7c7e0835e0c037"
-  integrity sha512-8u0+fBGWolDshsFgPQJESkDa72da/EVwvL+II0trN2DR66wMwiQ9/CihaGfHdlLGFzbBZwMykFtxuwFdZqlKwg==
+"@jest/transform@^29.5.0":
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.5.0.tgz#cf9c872d0965f0cbd32f1458aa44a2b1988b00f9"
+  integrity sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw==
   dependencies:
     "@babel/core" "^7.11.6"
-    "@jest/types" "^29.4.3"
+    "@jest/types" "^29.5.0"
     "@jridgewell/trace-mapping" "^0.3.15"
     babel-plugin-istanbul "^6.1.1"
     chalk "^4.0.0"
     convert-source-map "^2.0.0"
     fast-json-stable-stringify "^2.1.0"
     graceful-fs "^4.2.9"
-    jest-haste-map "^29.4.3"
+    jest-haste-map "^29.5.0"
     jest-regex-util "^29.4.3"
-    jest-util "^29.4.3"
+    jest-util "^29.5.0"
     micromatch "^4.0.4"
     pirates "^4.0.4"
     slash "^3.0.0"
@@ -1975,10 +2417,10 @@
     "@types/yargs" "^17.0.8"
     chalk "^4.0.0"
 
-"@jest/types@^29.4.3":
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.4.3.tgz#9069145f4ef09adf10cec1b2901b2d390031431f"
-  integrity sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==
+"@jest/types@^29.5.0":
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.5.0.tgz#f59ef9b031ced83047c67032700d8c807d6e1593"
+  integrity sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==
   dependencies:
     "@jest/schemas" "^29.4.3"
     "@types/istanbul-lib-coverage" "^2.0.0"
@@ -2027,7 +2469,7 @@
   resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
   integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
 
-"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.14", "@jridgewell/trace-mapping@^0.3.15", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9":
+"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.15", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9":
   version "0.3.17"
   resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985"
   integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==
@@ -2035,11 +2477,23 @@
     "@jridgewell/resolve-uri" "3.1.0"
     "@jridgewell/sourcemap-codec" "1.4.14"
 
+"@koale/useworker@^4.0.2":
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/@koale/useworker/-/useworker-4.0.2.tgz#cb540a2581cd6025307c3ca6685bc60748773e58"
+  integrity sha512-xPIPADtom8/3/4FLNj7MvNcBM/Z2FleH85Fdx2O869eoKW8+PoEgtSVvoxWjCWMA46Sm9A5/R1TyzNGc+yM0wg==
+  dependencies:
+    dequal "^1.0.0"
+
 "@leichtgewicht/ip-codec@^2.0.1":
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b"
   integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==
 
+"@linaria/core@3.0.0-beta.13":
+  version "3.0.0-beta.13"
+  resolved "https://registry.yarnpkg.com/@linaria/core/-/core-3.0.0-beta.13.tgz#049c5be5faa67e341e413a0f6b641d5d78d91056"
+  integrity sha512-3zEi5plBCOsEzUneRVuQb+2SAx3qaC1dj0FfFAI6zIJQoDWu0dlSwKijMRack7oO9tUWrchfj3OkKQAd1LBdVg==
+
 "@mattermost/react-native-paste-input@^0.6.0":
   version "0.6.2"
   resolved "https://registry.yarnpkg.com/@mattermost/react-native-paste-input/-/react-native-paste-input-0.6.2.tgz#783779bc60758927781270293f35d6b4f937cd07"
@@ -2090,6 +2544,22 @@
   resolved "https://registry.yarnpkg.com/@notifee/react-native/-/react-native-7.5.0.tgz#3d846b6a607a6352e798a3440cd3880c005a7d56"
   integrity sha512-FR1xzQI2KpMyLuM7YZyHHjCcOXbjAcBWMeRlarro8peujr6gJZSg4j2Aw4o8O4ifMHDopFrovwyK1uYBaB9MUg==
 
+"@npmcli/fs@^1.0.0":
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-1.1.1.tgz#72f719fe935e687c56a4faecf3c03d06ba593257"
+  integrity sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==
+  dependencies:
+    "@gar/promisify" "^1.0.1"
+    semver "^7.3.5"
+
+"@npmcli/move-file@^1.0.1":
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674"
+  integrity sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==
+  dependencies:
+    mkdirp "^1.0.4"
+    rimraf "^3.0.2"
+
 "@pmmmwh/react-refresh-webpack-plugin@^0.5.3":
   version "0.5.10"
   resolved "https://registry.yarnpkg.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.10.tgz#2eba163b8e7dbabb4ce3609ab5e32ab63dda3ef8"
@@ -2105,6 +2575,11 @@
     schema-utils "^3.0.0"
     source-map "^0.7.3"
 
+"@popperjs/core@^2.9.0":
+  version "2.11.6"
+  resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.6.tgz#cee20bd55e68a1720bdab363ecf0c821ded4cd45"
+  integrity sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==
+
 "@react-native-async-storage/async-storage@^1.15.15", "@react-native-async-storage/async-storage@^1.15.17", "@react-native-async-storage/async-storage@^1.17.6":
   version "1.17.11"
   resolved "https://registry.yarnpkg.com/@react-native-async-storage/async-storage/-/async-storage-1.17.11.tgz#7ec329c1b9f610e344602e806b04d7c928a2341d"
@@ -2340,7 +2815,7 @@
   resolved "https://registry.yarnpkg.com/@react-native/assets/-/assets-1.0.0.tgz#c6f9bf63d274bafc8e970628de24986b30a55c8e"
   integrity sha512-KrwSpS1tKI70wuKl68DwJZYEvXktDHdZMG0k2AXD/rJVSlB23/X2CB2cutVR0HwNMJIal9HOUOBB2rVfa6UGtQ==
 
-"@react-native/normalize-color@*", "@react-native/normalize-color@2.1.0":
+"@react-native/normalize-color@*", "@react-native/normalize-color@2.1.0", "@react-native/normalize-color@^2.0.0":
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/@react-native/normalize-color/-/normalize-color-2.1.0.tgz#939b87a9849e81687d3640c5efa2a486ac266f91"
   integrity sha512-Z1jQI2NpdFJCVgpY+8Dq/Bt3d+YUi1928Q+/CZm/oh66fzM0RUl54vvuXlPJKybH4pdCZey1eDTPaLHkMPNgWA==
@@ -2350,6 +2825,101 @@
   resolved "https://registry.yarnpkg.com/@react-native/polyfills/-/polyfills-2.0.0.tgz#4c40b74655c83982c8cf47530ee7dc13d957b6aa"
   integrity sha512-K0aGNn1TjalKj+65D7ycc1//H9roAQ51GJVk5ZJQFb2teECGmzd86bYDC0aYdbRf7gtovescq4Zt6FR0tgXiHQ==
 
+"@react-navigation/bottom-tabs@^6.5.7":
+  version "6.5.7"
+  resolved "https://registry.yarnpkg.com/@react-navigation/bottom-tabs/-/bottom-tabs-6.5.7.tgz#08470c96e0d11481422214bb98f0ff034038856c"
+  integrity sha512-9oZYyRu2z7+1pr2dX5V54rHFPmlj4ztwQxFe85zwpnGcPtGIsXj7VCIdlHnjRHJBBFCszvJGQpYY6/G2+DfD+A==
+  dependencies:
+    "@react-navigation/elements" "^1.3.17"
+    color "^4.2.3"
+    warn-once "^0.1.0"
+
+"@react-navigation/core@^6.4.8":
+  version "6.4.8"
+  resolved "https://registry.yarnpkg.com/@react-navigation/core/-/core-6.4.8.tgz#a18e106d3c59cdcfc4ce53f7344e219ed35c88ed"
+  integrity sha512-klZ9Mcf/P2j+5cHMoGyIeurEzyBM2Uq9+NoSFrF6sdV5iCWHLFhrCXuhbBiQ5wVLCKf4lavlkd/DDs47PXs9RQ==
+  dependencies:
+    "@react-navigation/routers" "^6.1.8"
+    escape-string-regexp "^4.0.0"
+    nanoid "^3.1.23"
+    query-string "^7.1.3"
+    react-is "^16.13.0"
+    use-latest-callback "^0.1.5"
+
+"@react-navigation/drawer@^6.6.2":
+  version "6.6.2"
+  resolved "https://registry.yarnpkg.com/@react-navigation/drawer/-/drawer-6.6.2.tgz#8206d00a4b89f1f30640147e0c230267bb83d9ed"
+  integrity sha512-6qt4guBdz7bkdo/8BLSCcFNdQdSPYyNn05D9cD+VCY3mGThSiD8bRiP9ju+64im7LsSU+bNWXaP8RxA/FtTVQg==
+  dependencies:
+    "@react-navigation/elements" "^1.3.17"
+    color "^4.2.3"
+    warn-once "^0.1.0"
+
+"@react-navigation/elements@^1.3.17":
+  version "1.3.17"
+  resolved "https://registry.yarnpkg.com/@react-navigation/elements/-/elements-1.3.17.tgz#9cb95765940f2841916fc71686598c22a3e4067e"
+  integrity sha512-sui8AzHm6TxeEvWT/NEXlz3egYvCUog4tlXA4Xlb2Vxvy3purVXDq/XsM56lJl344U5Aj/jDzkVanOTMWyk4UA==
+
+"@react-navigation/native-stack@^6.9.12":
+  version "6.9.12"
+  resolved "https://registry.yarnpkg.com/@react-navigation/native-stack/-/native-stack-6.9.12.tgz#a09fe43ab2fc4c82a1809e3953021d1da4ead85c"
+  integrity sha512-kS2zXCWP0Rgt7uWaCUKrRl7U2U1Gp19rM1kyRY2YzBPXhWGVPjQ2ygBp88CTQzjgy8M07H/79jvGiZ0mlEJI+g==
+  dependencies:
+    "@react-navigation/elements" "^1.3.17"
+    warn-once "^0.1.0"
+
+"@react-navigation/native@^6.1.6":
+  version "6.1.6"
+  resolved "https://registry.yarnpkg.com/@react-navigation/native/-/native-6.1.6.tgz#84ff5cf85b91f660470fa9407c06c8ee393d5792"
+  integrity sha512-14PmSy4JR8HHEk04QkxQ0ZLuqtiQfb4BV9kkMXD2/jI4TZ+yc43OnO6fQ2o9wm+Bq8pY3DxyerC2AjNUz+oH7Q==
+  dependencies:
+    "@react-navigation/core" "^6.4.8"
+    escape-string-regexp "^4.0.0"
+    fast-deep-equal "^3.1.3"
+    nanoid "^3.1.23"
+
+"@react-navigation/routers@^6.1.8":
+  version "6.1.8"
+  resolved "https://registry.yarnpkg.com/@react-navigation/routers/-/routers-6.1.8.tgz#ae56b2678dbb5abca5bd7c95d6a8d1abc767cba2"
+  integrity sha512-CEge+ZLhb1HBrSvv4RwOol7EKLW1QoqVIQlE9TN5MpxS/+VoQvP+cLbuz0Op53/iJfYhtXRFd1ZAd3RTRqto9w==
+  dependencies:
+    nanoid "^3.1.23"
+
+"@remirror/core-constants@^2.0.0":
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/@remirror/core-constants/-/core-constants-2.0.0.tgz#a52f89059d93955e00810023cc76b4f7db9650bf"
+  integrity sha512-vpePPMecHJllBqCWXl6+FIcZqS+tRUM2kSCCKFeEo1H3XUEv3ocijBIPhnlSAa7g6maX+12ATTgxrOsLpWVr2g==
+  dependencies:
+    "@babel/runtime" "^7.13.10"
+
+"@remirror/core-helpers@^2.0.1":
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/@remirror/core-helpers/-/core-helpers-2.0.1.tgz#6847666a009ada8c9b9f3a093c13a6d07a95d9bb"
+  integrity sha512-s8M1pn33aBUhduvD1QR02uUQMegnFkGaTr4c1iBzxTTyg0rbQstzuQ7Q8TkL6n64JtgCdJS9jLz2dONb2meBKQ==
+  dependencies:
+    "@babel/runtime" "^7.13.10"
+    "@linaria/core" "3.0.0-beta.13"
+    "@remirror/core-constants" "^2.0.0"
+    "@remirror/types" "^1.0.0"
+    "@types/object.omit" "^3.0.0"
+    "@types/object.pick" "^1.3.1"
+    "@types/throttle-debounce" "^2.1.0"
+    case-anything "^2.1.10"
+    dash-get "^1.0.2"
+    deepmerge "^4.2.2"
+    fast-deep-equal "^3.1.3"
+    make-error "^1.3.6"
+    object.omit "^3.0.0"
+    object.pick "^1.3.0"
+    throttle-debounce "^3.0.1"
+
+"@remirror/types@^1.0.0":
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/@remirror/types/-/types-1.0.0.tgz#cc8764440089a2ada71f149c409739575b73b12e"
+  integrity sha512-7HQbW7k8VxrAtfzs9FxwO6XSDabn8tSFDi1wwzShOnU+cvaYpfxu0ygyTk3TpXsag1hgFKY3ZIlAfB4WVz2LkQ==
+  dependencies:
+    type-fest "^2.0.0"
+
 "@rollup/plugin-babel@^5.2.0":
   version "5.3.1"
   resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283"
@@ -2393,9 +2963,9 @@
   integrity sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==
 
 "@segment/analytics-react-native@^2.10.1":
-  version "2.13.0"
-  resolved "https://registry.yarnpkg.com/@segment/analytics-react-native/-/analytics-react-native-2.13.0.tgz#2a612253219cd91ea3716f46d4fa837231d968d8"
-  integrity sha512-Yt2/t7jNcEaMaYRgeT8KqMsemzjIBaxx5jk01+WI/TXPf0P1OBizMjoxYH3Bpv7CzyEVbVSFlUSrJmD5ll9xtQ==
+  version "2.13.1"
+  resolved "https://registry.yarnpkg.com/@segment/analytics-react-native/-/analytics-react-native-2.13.1.tgz#c093c145cb1a5a87041fbbfc5fa61c3fb370787c"
+  integrity sha512-IRg0k8rVTNj2U4jpA0VKGioUKYAbvvYf3FNR8BsHDB3Jutmxdh/SPYAttFU1APJ9mQsqMEIEfocbDtwCAL0kAg==
   dependencies:
     "@react-native-async-storage/async-storage" "^1.15.17"
     "@segment/sovran-react-native" "^0.4.5"
@@ -2403,6 +2973,14 @@
     js-base64 "^3.7.2"
     react-native-uuid "^2.0.1"
 
+"@segment/loosely-validate-event@^2.0.0":
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/@segment/loosely-validate-event/-/loosely-validate-event-2.0.0.tgz#87dfc979e5b4e7b82c5f1d8b722dfd5d77644681"
+  integrity sha512-ZMCSfztDBqwotkl848ODgVcAmN4OItEWDCkshcKz0/W6gGSQayuuCtWV/MlodFivAZD793d6UgANd6wCXUfrIw==
+  dependencies:
+    component-type "^1.2.1"
+    join-component "^1.1.0"
+
 "@segment/sovran-react-native@^0.4.5":
   version "0.4.5"
   resolved "https://registry.yarnpkg.com/@segment/sovran-react-native/-/sovran-react-native-0.4.5.tgz#2ae057790623cfbd84eb15e4a3bb9835d108f815"
@@ -2588,7 +3166,7 @@
     "@svgr/plugin-svgo" "^5.5.0"
     loader-utils "^2.0.0"
 
-"@testing-library/jest-native@^5.3.3":
+"@testing-library/jest-native@^5.4.1":
   version "5.4.2"
   resolved "https://registry.yarnpkg.com/@testing-library/jest-native/-/jest-native-5.4.2.tgz#6b0c987cc57f8d900763e763025d00d26ccbc85f"
   integrity sha512-Vo/CE1uvCVH1H8YPoOEXLXVsm+BjzSQTq35+wkri1fr0O5D+A2WZ+m3ni5g6f1OCzNKNGIAHmisBEWkDs1P1mw==
@@ -2599,12 +3177,101 @@
     pretty-format "^29.0.3"
     redent "^3.0.0"
 
-"@testing-library/react-native@^11.5.0":
-  version "11.5.2"
-  resolved "https://registry.yarnpkg.com/@testing-library/react-native/-/react-native-11.5.2.tgz#64238431de1ce4e128810bf8277a7cc118f1c812"
-  integrity sha512-haa/82Sy4ngAbXF6VTw5U306ZQHWfv7OKxALA4AkMKnX+6frhSd9aAe9rp50CyI2XJsNna0Rh6G+CSw+SPztYg==
+"@testing-library/react-native@^11.5.2":
+  version "11.5.4"
+  resolved "https://registry.yarnpkg.com/@testing-library/react-native/-/react-native-11.5.4.tgz#5c57c0c5afc3f1960ff491aba68f66adc899e1cc"
+  integrity sha512-6DRzMHqili5pp1JRm7fh80SAP48aR25b37gohlLoKbqGBpDw8BbZTad7Luzktm9MU5DQUm7xyzg07Z1CyveVFA==
   dependencies:
-    pretty-format "^29.4.0"
+    pretty-format "^29.0.0"
+
+"@tiptap/core@^2.0.0-beta.220":
+  version "2.0.0-beta.220"
+  resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.0.0-beta.220.tgz#ced4b8f13ad6361f957275510bd0c005de29d18c"
+  integrity sha512-F2Q666xJqijBU5o+GqekqseNgIEMTs6BhsLDaf9DwThhljGLS8RXKnSvQxrxLNrYEPpw39n/G3Qt8YAOk5qR6w==
+
+"@tiptap/extension-bubble-menu@^2.0.0-beta.220":
+  version "2.0.0-beta.220"
+  resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.0.0-beta.220.tgz#3fea0c846f73a237f562fdce05671ef1fa025943"
+  integrity sha512-wthyec7s0vZlTSEAAZEgoFfx/1Arwg1zxDUrrE+YAost/Yn+w4xQksz/ts5Bx90iOk2qsJ+jzzttLRV17Ku7lA==
+  dependencies:
+    lodash "^4.17.21"
+    tippy.js "^6.3.7"
+
+"@tiptap/extension-document@^2.0.0-beta.220":
+  version "2.0.0-beta.220"
+  resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-2.0.0-beta.220.tgz#15b4db7a92659eff7efc6d4d877dcf72e3fd61b6"
+  integrity sha512-2sja4ZvOb4iynHrzinnclCSFgLyo6fJc1fBV5fIYaOgZOYcvz9KK8fgKiq+wIpG58sJEmQ5kcwwBlkXv+NTK+g==
+
+"@tiptap/extension-floating-menu@^2.0.0-beta.220":
+  version "2.0.0-beta.220"
+  resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-2.0.0-beta.220.tgz#35eb154227533ada738c922be2f8cf18426fe4bf"
+  integrity sha512-+WfcBEedm82ntaVIEQAGz0Om96Rpav7a+4f7e8N4PrLKm6nZ3gBaEkZVQ6vjJ6S/1htiWCv1XosYIwRboPBG0w==
+  dependencies:
+    tippy.js "^6.3.7"
+
+"@tiptap/extension-link@^2.0.0-beta.220":
+  version "2.0.0-beta.220"
+  resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-2.0.0-beta.220.tgz#c9954613cd1e0a0f1527853b732ef50dff734eac"
+  integrity sha512-vjEA8cE37ZZVVgPHSpttw3kbJoClb+ya/BVukDtJ1h6C7mIR1rqzNxTgpbnXJuA8xww0JOjpa5dpzEgcs294fA==
+  dependencies:
+    linkifyjs "^4.1.0"
+
+"@tiptap/extension-mention@^2.0.0-beta.220":
+  version "2.0.0-beta.220"
+  resolved "https://registry.yarnpkg.com/@tiptap/extension-mention/-/extension-mention-2.0.0-beta.220.tgz#c3745895096157b09412bd49544f4ae741e8d0da"
+  integrity sha512-mjFNBuLxLaZ48CaIp/AdyHB2X1UKptpv6NVG0JaP2vBxW22eUy709JmCbRnWjeYe8pHbJjW22WC4/M1C44SFWg==
+
+"@tiptap/extension-paragraph@^2.0.0-beta.220":
+  version "2.0.0-beta.220"
+  resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-2.0.0-beta.220.tgz#d552dfdeeab9856e9eb8f0a7cf850f37d7cced69"
+  integrity sha512-ZGCzNGFYV4wa3l1nXtDIaYp7O6f0DrGTSl3alKkDTQe3SOmzXS2HjgWl9yPw8VXpU9W5mMGhXd+nGn/jUk+f/A==
+
+"@tiptap/extension-placeholder@^2.0.0-beta.220":
+  version "2.0.0-beta.220"
+  resolved "https://registry.yarnpkg.com/@tiptap/extension-placeholder/-/extension-placeholder-2.0.0-beta.220.tgz#1d6057e5ae950d9a1ed43c03d26df60c08368f87"
+  integrity sha512-Pq79BH/JqhjTNgxHkmbzcmwATsSJdRRSLHrnLx5upSmwEkQwCzqni9jL10rL2NM1ZyR+o25xC+r5loujx0aQ+Q==
+
+"@tiptap/extension-text@^2.0.0-beta.220":
+  version "2.0.0-beta.220"
+  resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-2.0.0-beta.220.tgz#3f51d4aac11c16d79cf8ca22502898b67f5bc2f5"
+  integrity sha512-3tnffc2YMjNyv7Lbad6fx9wYDE/Buz8vhx76M2AOSrjYbzmTJf7mLkgdlPM0VTy7FGZD5CGgHJAgYNt5HIqPkQ==
+
+"@tiptap/pm@^2.0.0-beta.220":
+  version "2.0.0-beta.220"
+  resolved "https://registry.yarnpkg.com/@tiptap/pm/-/pm-2.0.0-beta.220.tgz#04e4c98e4d042ea8d67148ec6676f7078c6bac5a"
+  integrity sha512-O9mGcmwUpEr630HY9RylIyZJKnpXi3xWINWNiAEfRJ1br5j5pHRoVRJQ1HzU+6+Z+i/8qp3zRHGLTBqihaZETA==
+  dependencies:
+    prosemirror-changeset "^2.2.0"
+    prosemirror-collab "^1.3.0"
+    prosemirror-commands "^1.3.1"
+    prosemirror-dropcursor "^1.5.0"
+    prosemirror-gapcursor "^1.3.1"
+    prosemirror-history "^1.3.0"
+    prosemirror-inputrules "^1.2.0"
+    prosemirror-keymap "^1.2.0"
+    prosemirror-markdown "^1.10.1"
+    prosemirror-menu "^1.2.1"
+    prosemirror-model "^1.18.1"
+    prosemirror-schema-basic "^1.2.0"
+    prosemirror-schema-list "^1.2.2"
+    prosemirror-state "^1.4.1"
+    prosemirror-tables "^1.3.0"
+    prosemirror-trailing-node "^2.0.2"
+    prosemirror-transform "^1.7.0"
+    prosemirror-view "^1.28.2"
+
+"@tiptap/react@^2.0.0-beta.220":
+  version "2.0.0-beta.220"
+  resolved "https://registry.yarnpkg.com/@tiptap/react/-/react-2.0.0-beta.220.tgz#c79df680ee2002061078704e4f35b232588a4a20"
+  integrity sha512-AZWaCGjm2FcJWNl1dxRCHOjGYvUV8R39L7tAcnKxHGajOHdFk8JQHc0XbVZhdBi2YgwvwEr7Tw9G2lzi9e6/fg==
+  dependencies:
+    "@tiptap/extension-bubble-menu" "^2.0.0-beta.220"
+    "@tiptap/extension-floating-menu" "^2.0.0-beta.220"
+
+"@tiptap/suggestion@^2.0.0-beta.220":
+  version "2.0.0-beta.220"
+  resolved "https://registry.yarnpkg.com/@tiptap/suggestion/-/suggestion-2.0.0-beta.220.tgz#2dc05f65e89006ffaad9f2b6a3468311a305e5ee"
+  integrity sha512-lYb2HOAKJLjEBbTx5VXA32wRryQiMwaKkNfr3v6UhlwoNgD6NkCYID08UJbpMV7iM+iFQp9408D/vVWFwvOuKg==
 
 "@tokenizer/token@^0.3.0":
   version "0.3.0"
@@ -2616,6 +3283,11 @@
   resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
   integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==
 
+"@tootallnate/once@2":
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf"
+  integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==
+
 "@trysound/sax@0.2.0":
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad"
@@ -2739,6 +3411,14 @@
     "@types/qs" "*"
     "@types/serve-static" "*"
 
+"@types/glob@^7.1.1":
+  version "7.2.0"
+  resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb"
+  integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==
+  dependencies:
+    "@types/minimatch" "*"
+    "@types/node" "*"
+
 "@types/graceful-fs@^4.1.2", "@types/graceful-fs@^4.1.3":
   version "4.1.6"
   resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.6.tgz#e14b2576a1c25026b7f02ede1de3b84c3a1efeae"
@@ -2787,13 +3467,22 @@
   dependencies:
     "@types/istanbul-lib-report" "*"
 
-"@types/jest@^26.0.23":
-  version "26.0.24"
-  resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.24.tgz#943d11976b16739185913a1936e0de0c4a7d595a"
-  integrity sha512-E/X5Vib8BWqZNRlDxj9vYXhsDwPYbPINqKF9BsnSoon4RQ0D9moEuLD8txgyypFLH7J4+Lho9Nr/c8H0Fi+17w==
+"@types/jest@^29.4.0":
+  version "29.4.0"
+  resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.4.0.tgz#a8444ad1704493e84dbf07bb05990b275b3b9206"
+  integrity sha512-VaywcGQ9tPorCX/Jkkni7RWGFfI11whqzs8dvxF41P17Z+z872thvEvlIbznjPJ02kl1HMX3LmLOonsj2n7HeQ==
+  dependencies:
+    expect "^29.0.0"
+    pretty-format "^29.0.0"
+
+"@types/jsdom@^20.0.0":
+  version "20.0.1"
+  resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-20.0.1.tgz#07c14bc19bd2f918c1929541cdaacae894744808"
+  integrity sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==
   dependencies:
-    jest-diff "^26.0.0"
-    pretty-format "^26.0.0"
+    "@types/node" "*"
+    "@types/tough-cookie" "*"
+    parse5 "^7.0.0"
 
 "@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
   version "7.0.11"
@@ -2857,15 +3546,25 @@
   resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10"
   integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==
 
+"@types/minimatch@*":
+  version "5.1.2"
+  resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca"
+  integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==
+
 "@types/node@*":
-  version "18.14.2"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-18.14.2.tgz#c076ed1d7b6095078ad3cf21dfeea951842778b1"
-  integrity sha512-1uEQxww3DaghA0RxqHx0O0ppVlo43pJhepY51OxuQIKHpjbnYLA7vcdwioNPzIqmC2u3I/dmylcqjlh0e7AyUA==
+  version "18.14.6"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-18.14.6.tgz#ae1973dd2b1eeb1825695bb11ebfb746d27e3e93"
+  integrity sha512-93+VvleD3mXwlLI/xASjw0FzKcwzl3OdTCzm1LaRfqgS21gfFtK3zDXM5Op9TeeMsJVOaJ2VRDpT9q4Y3d0AvA==
 
-"@types/normalize-package-data@^2.4.0":
-  version "2.4.1"
-  resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301"
-  integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==
+"@types/object.omit@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@types/object.omit/-/object.omit-3.0.0.tgz#0d31e1208eac8fe2ad5c9499a1016a8273bbfafc"
+  integrity sha512-I27IoPpH250TUzc9FzXd0P1BV/BMJuzqD3jOz98ehf9dQqGkxlq+hO1bIqZGWqCg5bVOy0g4AUVJtnxe0klDmw==
+
+"@types/object.pick@^1.3.1":
+  version "1.3.2"
+  resolved "https://registry.yarnpkg.com/@types/object.pick/-/object.pick-1.3.2.tgz#9eb28118240ad8f658b9c9c6caf35359fdb37150"
+  integrity sha512-sn7L+qQ6RLPdXRoiaE7bZ/Ek+o4uICma/lBFPyJEKDTPTBP1W8u0c4baj3EiS4DiqLs+Hk+KUGvMVJtAw3ePJg==
 
 "@types/parse-json@^4.0.0":
   version "4.0.0"
@@ -2976,6 +3675,16 @@
   resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c"
   integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==
 
+"@types/throttle-debounce@^2.1.0":
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/@types/throttle-debounce/-/throttle-debounce-2.1.0.tgz#1c3df624bfc4b62f992d3012b84c56d41eab3776"
+  integrity sha512-5eQEtSCoESnh2FsiLTxE121IiE60hnMqcb435fShf4bpLRjEu1Eoekht23y6zXS9Ts3l+Szu3TARnTsA0GkOkQ==
+
+"@types/tough-cookie@*":
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.2.tgz#6286b4c7228d58ab7866d19716f3696e03a09397"
+  integrity sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==
+
 "@types/trusted-types@^2.0.2":
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.3.tgz#a136f83b0758698df454e328759dbd3d44555311"
@@ -3015,13 +3724,13 @@
     "@types/yargs-parser" "*"
 
 "@typescript-eslint/eslint-plugin@^5.30.5", "@typescript-eslint/eslint-plugin@^5.48.2", "@typescript-eslint/eslint-plugin@^5.5.0":
-  version "5.53.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.53.0.tgz#24b8b4a952f3c615fe070e3c461dd852b5056734"
-  integrity sha512-alFpFWNucPLdUOySmXCJpzr6HKC3bu7XooShWM+3w/EL6J2HIoB2PFxpLnq4JauWVk6DiVeNKzQlFEaE+X9sGw==
+  version "5.54.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.54.1.tgz#0c5091289ce28372e38ab8d28e861d2dbe1ab29e"
+  integrity sha512-a2RQAkosH3d3ZIV08s3DcL/mcGc2M/UC528VkPULFxR9VnVPT8pBu0IyBAJJmVsCmhVfwQX1v6q+QGnmSe1bew==
   dependencies:
-    "@typescript-eslint/scope-manager" "5.53.0"
-    "@typescript-eslint/type-utils" "5.53.0"
-    "@typescript-eslint/utils" "5.53.0"
+    "@typescript-eslint/scope-manager" "5.54.1"
+    "@typescript-eslint/type-utils" "5.54.1"
+    "@typescript-eslint/utils" "5.54.1"
     debug "^4.3.4"
     grapheme-splitter "^1.0.4"
     ignore "^5.2.0"
@@ -3031,78 +3740,78 @@
     tsutils "^3.21.0"
 
 "@typescript-eslint/experimental-utils@^5.0.0":
-  version "5.53.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.53.0.tgz#e249e3a47ace290ea3d83a5a08c8d90cd7fe2a53"
-  integrity sha512-4SklZEwRn0jqkhtW+pPZpbKFXprwGneBndRM0TGzJu/LWdb9QV2hBgFIVU9AREo02BzqFvyG/ypd+xAW5YGhXw==
+  version "5.54.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.54.1.tgz#a45609ce43fc6b24b4c4dde215446eaad7805223"
+  integrity sha512-oqSc2Gr4TL/2M0XRJ9abA1o3Wf1cFJTNqWq0kjdStIIvgMQGZ3TSaFFJ2Cvy3Fgqi9UfDZ8u5idbACssIIyHaw==
   dependencies:
-    "@typescript-eslint/utils" "5.53.0"
+    "@typescript-eslint/utils" "5.54.1"
 
 "@typescript-eslint/parser@^5.30.5", "@typescript-eslint/parser@^5.48.2", "@typescript-eslint/parser@^5.5.0":
-  version "5.53.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.53.0.tgz#a1f2b9ae73b83181098747e96683f1b249ecab52"
-  integrity sha512-MKBw9i0DLYlmdOb3Oq/526+al20AJZpANdT6Ct9ffxcV8nKCHz63t/S0IhlTFNsBIHJv+GY5SFJ0XfqVeydQrQ==
+  version "5.54.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.54.1.tgz#05761d7f777ef1c37c971d3af6631715099b084c"
+  integrity sha512-8zaIXJp/nG9Ff9vQNh7TI+C3nA6q6iIsGJ4B4L6MhZ7mHnTMR4YP5vp2xydmFXIy8rpyIVbNAG44871LMt6ujg==
   dependencies:
-    "@typescript-eslint/scope-manager" "5.53.0"
-    "@typescript-eslint/types" "5.53.0"
-    "@typescript-eslint/typescript-estree" "5.53.0"
+    "@typescript-eslint/scope-manager" "5.54.1"
+    "@typescript-eslint/types" "5.54.1"
+    "@typescript-eslint/typescript-estree" "5.54.1"
     debug "^4.3.4"
 
-"@typescript-eslint/scope-manager@5.53.0":
-  version "5.53.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.53.0.tgz#42b54f280e33c82939275a42649701024f3fafef"
-  integrity sha512-Opy3dqNsp/9kBBeCPhkCNR7fmdSQqA+47r21hr9a14Bx0xnkElEQmhoHga+VoaoQ6uDHjDKmQPIYcUcKJifS7w==
+"@typescript-eslint/scope-manager@5.54.1":
+  version "5.54.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.54.1.tgz#6d864b4915741c608a58ce9912edf5a02bb58735"
+  integrity sha512-zWKuGliXxvuxyM71UA/EcPxaviw39dB2504LqAmFDjmkpO8qNLHcmzlh6pbHs1h/7YQ9bnsO8CCcYCSA8sykUg==
   dependencies:
-    "@typescript-eslint/types" "5.53.0"
-    "@typescript-eslint/visitor-keys" "5.53.0"
+    "@typescript-eslint/types" "5.54.1"
+    "@typescript-eslint/visitor-keys" "5.54.1"
 
-"@typescript-eslint/type-utils@5.53.0":
-  version "5.53.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.53.0.tgz#41665449935ba9b4e6a1ba6e2a3f4b2c31d6cf97"
-  integrity sha512-HO2hh0fmtqNLzTAme/KnND5uFNwbsdYhCZghK2SoxGp3Ifn2emv+hi0PBUjzzSh0dstUIFqOj3bp0AwQlK4OWw==
+"@typescript-eslint/type-utils@5.54.1":
+  version "5.54.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.54.1.tgz#4825918ec27e55da8bb99cd07ec2a8e5f50ab748"
+  integrity sha512-WREHsTz0GqVYLIbzIZYbmUUr95DKEKIXZNH57W3s+4bVnuF1TKe2jH8ZNH8rO1CeMY3U4j4UQeqPNkHMiGem3g==
   dependencies:
-    "@typescript-eslint/typescript-estree" "5.53.0"
-    "@typescript-eslint/utils" "5.53.0"
+    "@typescript-eslint/typescript-estree" "5.54.1"
+    "@typescript-eslint/utils" "5.54.1"
     debug "^4.3.4"
     tsutils "^3.21.0"
 
-"@typescript-eslint/types@5.53.0":
-  version "5.53.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.53.0.tgz#f79eca62b97e518ee124086a21a24f3be267026f"
-  integrity sha512-5kcDL9ZUIP756K6+QOAfPkigJmCPHcLN7Zjdz76lQWWDdzfOhZDTj1irs6gPBKiXx5/6O3L0+AvupAut3z7D2A==
+"@typescript-eslint/types@5.54.1":
+  version "5.54.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.54.1.tgz#29fbac29a716d0f08c62fe5de70c9b6735de215c"
+  integrity sha512-G9+1vVazrfAfbtmCapJX8jRo2E4MDXxgm/IMOF4oGh3kq7XuK3JRkOg6y2Qu1VsTRmWETyTkWt1wxy7X7/yLkw==
 
-"@typescript-eslint/typescript-estree@5.53.0":
-  version "5.53.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.53.0.tgz#bc651dc28cf18ab248ecd18a4c886c744aebd690"
-  integrity sha512-eKmipH7QyScpHSkhbptBBYh9v8FxtngLquq292YTEQ1pxVs39yFBlLC1xeIZcPPz1RWGqb7YgERJRGkjw8ZV7w==
+"@typescript-eslint/typescript-estree@5.54.1":
+  version "5.54.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.54.1.tgz#df7b6ae05fd8fef724a87afa7e2f57fa4a599be1"
+  integrity sha512-bjK5t+S6ffHnVwA0qRPTZrxKSaFYocwFIkZx5k7pvWfsB1I57pO/0M0Skatzzw1sCkjJ83AfGTL0oFIFiDX3bg==
   dependencies:
-    "@typescript-eslint/types" "5.53.0"
-    "@typescript-eslint/visitor-keys" "5.53.0"
+    "@typescript-eslint/types" "5.54.1"
+    "@typescript-eslint/visitor-keys" "5.54.1"
     debug "^4.3.4"
     globby "^11.1.0"
     is-glob "^4.0.3"
     semver "^7.3.7"
     tsutils "^3.21.0"
 
-"@typescript-eslint/utils@5.53.0", "@typescript-eslint/utils@^5.10.0", "@typescript-eslint/utils@^5.43.0":
-  version "5.53.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.53.0.tgz#e55eaad9d6fffa120575ffaa530c7e802f13bce8"
-  integrity sha512-VUOOtPv27UNWLxFwQK/8+7kvxVC+hPHNsJjzlJyotlaHjLSIgOCKj9I0DBUjwOOA64qjBwx5afAPjksqOxMO0g==
+"@typescript-eslint/utils@5.54.1", "@typescript-eslint/utils@^5.10.0", "@typescript-eslint/utils@^5.43.0":
+  version "5.54.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.54.1.tgz#7a3ee47409285387b9d4609ea7e1020d1797ec34"
+  integrity sha512-IY5dyQM8XD1zfDe5X8jegX6r2EVU5o/WJnLu/znLPWCBF7KNGC+adacXnt5jEYS9JixDcoccI6CvE4RCjHMzCQ==
   dependencies:
     "@types/json-schema" "^7.0.9"
     "@types/semver" "^7.3.12"
-    "@typescript-eslint/scope-manager" "5.53.0"
-    "@typescript-eslint/types" "5.53.0"
-    "@typescript-eslint/typescript-estree" "5.53.0"
+    "@typescript-eslint/scope-manager" "5.54.1"
+    "@typescript-eslint/types" "5.54.1"
+    "@typescript-eslint/typescript-estree" "5.54.1"
     eslint-scope "^5.1.1"
     eslint-utils "^3.0.0"
     semver "^7.3.7"
 
-"@typescript-eslint/visitor-keys@5.53.0":
-  version "5.53.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.53.0.tgz#8a5126623937cdd909c30d8fa72f79fa56cc1a9f"
-  integrity sha512-JqNLnX3leaHFZEN0gCh81sIvgrp/2GOACZNgO4+Tkf64u51kTpAyWFOY8XHx8XuXr3N2C9zgPPHtcpMg6z1g0w==
+"@typescript-eslint/visitor-keys@5.54.1":
+  version "5.54.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.54.1.tgz#d7a8a0f7181d6ac748f4d47b2306e0513b98bf8b"
+  integrity sha512-q8iSoHTgwCfgcRJ2l2x+xCbu8nBlRAlsQ33k24Adj8eoVBE0f8dUeI+bAa8F84Mv05UGbAx57g2zrRsYIooqQg==
   dependencies:
-    "@typescript-eslint/types" "5.53.0"
+    "@typescript-eslint/types" "5.54.1"
     eslint-visitor-keys "^3.3.0"
 
 "@ucans/core@0.11.0":
@@ -3112,6 +3821,29 @@
   dependencies:
     uint8arrays "3.0.0"
 
+"@urql/core@2.3.6":
+  version "2.3.6"
+  resolved "https://registry.yarnpkg.com/@urql/core/-/core-2.3.6.tgz#ee0a6f8fde02251e9560c5f17dce5cd90f948552"
+  integrity sha512-PUxhtBh7/8167HJK6WqBv6Z0piuiaZHQGYbhwpNL9aIQmLROPEdaUYkY4wh45wPQXcTpnd11l0q3Pw+TI11pdw==
+  dependencies:
+    "@graphql-typed-document-node/core" "^3.1.0"
+    wonka "^4.0.14"
+
+"@urql/core@>=2.3.1":
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/@urql/core/-/core-3.1.1.tgz#a49cd572360d01f2469a786b294fba2269a65e53"
+  integrity sha512-Mnxtq4I4QeFJsgs7Iytw+HyhiGxISR6qtyk66c9tipozLZ6QVxrCiUPF2HY4BxNIabaxcp+rivadvm8NAnXj4Q==
+  dependencies:
+    wonka "^6.1.2"
+
+"@urql/exchange-retry@0.3.0":
+  version "0.3.0"
+  resolved "https://registry.yarnpkg.com/@urql/exchange-retry/-/exchange-retry-0.3.0.tgz#13252108b5a111aab45f9982f4db18d1a286e423"
+  integrity sha512-hHqer2mcdVC0eYnVNbWyi28AlGOPb2vjH3lP3/Bc8Lc8BjhMsDwFMm7WhoP5C1+cfbr/QJ6Er3H/L08wznXxfg==
+  dependencies:
+    "@urql/core" ">=2.3.1"
+    wonka "^4.0.14"
+
 "@webassemblyjs/ast@1.11.1":
   version "1.11.1"
   resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7"
@@ -3248,6 +3980,11 @@
   resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-2.0.1.tgz#34bdc31727a1889198855913db2f270ace6d7bf8"
   integrity sha512-0G7tNyS+yW8TdgHwZKlDWYXFA6OJQnoLCQvYKkQP0Q2X205PSQ6RNUj0M+1OB/9gRQaUZ/ccYfaxd0nhaWKfjw==
 
+"@xmldom/xmldom@~0.7.0", "@xmldom/xmldom@~0.7.7":
+  version "0.7.9"
+  resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.7.9.tgz#7f9278a50e737920e21b297b8a35286e9942c056"
+  integrity sha512-yceMpm/xd4W2a85iqZyO09gTnHvXF6pyiWjD2jcOJs7hRoZtNNOO1eJlhHj1ixA+xip2hOyGn+LgcvLCMo5zXA==
+
 "@xtuc/ieee754@^1.2.0":
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
@@ -3263,7 +4000,7 @@
   resolved "https://registry.yarnpkg.com/@zxing/text-encoding/-/text-encoding-0.9.0.tgz#fb50ffabc6c7c66a0c96b4c03e3d9be74864b70b"
   integrity sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==
 
-abab@^2.0.3, abab@^2.0.5:
+abab@^2.0.3, abab@^2.0.5, abab@^2.0.6:
   version "2.0.6"
   resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291"
   integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==
@@ -3280,7 +4017,7 @@ absolute-path@^0.0.0:
   resolved "https://registry.yarnpkg.com/absolute-path/-/absolute-path-0.0.0.tgz#a78762fbdadfb5297be99b15d35a785b2f095bf7"
   integrity sha512-HQiug4c+/s3WOvEnDRxXVmNtSG5s2gJM9r19BTcqjp7BWcE48PB+Y2G6jE65kqI0LpsQeMZygt/b60Gi4KxGyA==
 
-accepts@^1.3.7, accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7, accepts@~1.3.8:
+accepts@^1.3.7, accepts@^1.3.8, accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7, accepts@~1.3.8:
   version "1.3.8"
   resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
   integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==
@@ -3296,6 +4033,14 @@ acorn-globals@^6.0.0:
     acorn "^7.1.1"
     acorn-walk "^7.1.1"
 
+acorn-globals@^7.0.0:
+  version "7.0.1"
+  resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-7.0.1.tgz#0dbf05c44fa7c94332914c02066d5beff62c40c3"
+  integrity sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==
+  dependencies:
+    acorn "^8.1.0"
+    acorn-walk "^8.0.2"
+
 acorn-import-assertions@^1.7.6:
   version "1.8.0"
   resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9"
@@ -3320,12 +4065,17 @@ acorn-walk@^7.0.0, acorn-walk@^7.1.1:
   resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc"
   integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==
 
+acorn-walk@^8.0.2:
+  version "8.2.0"
+  resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1"
+  integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==
+
 acorn@^7.0.0, acorn@^7.1.1:
   version "7.4.1"
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
   integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
 
-acorn@^8.2.4, acorn@^8.5.0, acorn@^8.7.1, acorn@^8.8.0:
+acorn@^8.1.0, acorn@^8.2.4, acorn@^8.5.0, acorn@^8.7.1, acorn@^8.8.0, acorn@^8.8.1:
   version "8.8.2"
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a"
   integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==
@@ -3350,6 +4100,14 @@ agent-base@6:
   dependencies:
     debug "4"
 
+aggregate-error@^3.0.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a"
+  integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==
+  dependencies:
+    clean-stack "^2.0.0"
+    indent-string "^4.0.0"
+
 ajv-formats@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520"
@@ -3394,13 +4152,25 @@ anser@^1.4.9:
   resolved "https://registry.yarnpkg.com/anser/-/anser-1.4.10.tgz#befa3eddf282684bd03b63dcda3927aef8c2e35b"
   integrity sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==
 
-ansi-escapes@^4.2.1, ansi-escapes@^4.3.1:
+ansi-escapes@^3.1.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b"
+  integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==
+
+ansi-escapes@^4.2.1, ansi-escapes@^4.3.0, ansi-escapes@^4.3.1:
   version "4.3.2"
   resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e"
   integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==
   dependencies:
     type-fest "^0.21.3"
 
+ansi-escapes@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-6.0.0.tgz#68c580e87a489f6df3d761028bb93093fde6bd8a"
+  integrity sha512-IG23inYII3dWlU2EyiAiGj6Bwal5GzsgPMwjYGvc1HPE2dgbj4ZB5ToWBKSquKw74nB3TIuOwaI6/jSULzfgrw==
+  dependencies:
+    type-fest "^3.0.0"
+
 ansi-fragments@^0.2.1:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/ansi-fragments/-/ansi-fragments-0.2.1.tgz#24409c56c4cc37817c3d7caa99d8969e2de5a05e"
@@ -3449,6 +4219,11 @@ ansi-styles@^5.0.0:
   resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b"
   integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==
 
+any-promise@^1.0.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
+  integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==
+
 anymatch@^3.0.3, anymatch@~3.1.2:
   version "3.1.3"
   resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e"
@@ -3462,6 +4237,16 @@ appdirsjs@^1.2.4:
   resolved "https://registry.yarnpkg.com/appdirsjs/-/appdirsjs-1.2.7.tgz#50b4b7948a26ba6090d4aede2ae2dc2b051be3b3"
   integrity sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw==
 
+application-config-path@^0.1.0:
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/application-config-path/-/application-config-path-0.1.1.tgz#8b5ac64ff6afdd9bd70ce69f6f64b6998f5f756e"
+  integrity sha512-zy9cHePtMP0YhwG+CfHm0bgwdnga2X3gZexpdCwEj//dpb+TKajtiC8REEUJUSq6Ab4f9cgNy2l8ObXzCXFkEw==
+
+arg@4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.0.tgz#583c518199419e0037abb74062c37f8519e575f0"
+  integrity sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==
+
 arg@^5.0.2:
   version "5.0.2"
   resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c"
@@ -3522,11 +4307,28 @@ array-includes@^3.1.5, array-includes@^3.1.6:
     get-intrinsic "^1.1.3"
     is-string "^1.0.7"
 
+array-union@^1.0.1:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39"
+  integrity sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==
+  dependencies:
+    array-uniq "^1.0.1"
+
 array-union@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
   integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
 
+array-union@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/array-union/-/array-union-3.0.1.tgz#da52630d327f8b88cfbfb57728e2af5cd9b6b975"
+  integrity sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==
+
+array-uniq@^1.0.1:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6"
+  integrity sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==
+
 array-unique@^0.3.2:
   version "0.3.2"
   resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
@@ -3706,15 +4508,15 @@ babel-jest@^27.4.2, babel-jest@^27.5.1:
     graceful-fs "^4.2.9"
     slash "^3.0.0"
 
-babel-jest@^29.2.1, babel-jest@^29.4.3:
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.4.3.tgz#478b84d430972b277ad67dd631be94abea676792"
-  integrity sha512-o45Wyn32svZE+LnMVWv/Z4x0SwtLbh4FyGcYtR20kIWd+rdrDZ9Fzq8Ml3MYLD+mZvEdzCjZsCnYZ2jpJyQ+Nw==
+babel-jest@^29.2.1, babel-jest@^29.4.2, babel-jest@^29.5.0:
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.5.0.tgz#3fe3ddb109198e78b1c88f9ebdecd5e4fc2f50a5"
+  integrity sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q==
   dependencies:
-    "@jest/transform" "^29.4.3"
+    "@jest/transform" "^29.5.0"
     "@types/babel__core" "^7.1.14"
     babel-plugin-istanbul "^6.1.1"
-    babel-preset-jest "^29.4.3"
+    babel-preset-jest "^29.5.0"
     chalk "^4.0.0"
     graceful-fs "^4.2.9"
     slash "^3.0.0"
@@ -3758,10 +4560,10 @@ babel-plugin-jest-hoist@^27.5.1:
     "@types/babel__core" "^7.0.0"
     "@types/babel__traverse" "^7.0.6"
 
-babel-plugin-jest-hoist@^29.4.3:
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.4.3.tgz#ad1dfb5d31940957e00410ef7d9b2aa94b216101"
-  integrity sha512-mB6q2q3oahKphy5V7CpnNqZOCkxxZ9aokf1eh82Dy3jQmg4xvM1tGrh5y6BQUJh4a3Pj9+eLfwvAZ7VNKg7H8Q==
+babel-plugin-jest-hoist@^29.5.0:
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz#a97db437936f441ec196990c9738d4b88538618a"
+  integrity sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==
   dependencies:
     "@babel/template" "^7.3.3"
     "@babel/types" "^7.3.3"
@@ -3777,6 +4579,17 @@ babel-plugin-macros@^3.1.0:
     cosmiconfig "^7.0.0"
     resolve "^1.19.0"
 
+babel-plugin-module-resolver@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/babel-plugin-module-resolver/-/babel-plugin-module-resolver-4.1.0.tgz#22a4f32f7441727ec1fbf4967b863e1e3e9f33e2"
+  integrity sha512-MlX10UDheRr3lb3P0WcaIdtCSRlxdQsB1sBqL7W0raF070bGl1HQQq5K3T2vf2XAYie+ww+5AKC/WrkjRO2knA==
+  dependencies:
+    find-babel-config "^1.2.0"
+    glob "^7.1.6"
+    pkg-up "^3.1.0"
+    reselect "^4.0.0"
+    resolve "^1.13.1"
+
 babel-plugin-module-resolver@^5.0.0:
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-module-resolver/-/babel-plugin-module-resolver-5.0.0.tgz#2b7fc176bd55da25f516abf96015617b4f70fc73"
@@ -3817,7 +4630,7 @@ babel-plugin-polyfill-regenerator@^0.4.1:
   dependencies:
     "@babel/helper-define-polyfill-provider" "^0.3.3"
 
-babel-plugin-react-native-web@^0.18.12:
+babel-plugin-react-native-web@^0.18.12, babel-plugin-react-native-web@~0.18.10:
   version "0.18.12"
   resolved "https://registry.yarnpkg.com/babel-plugin-react-native-web/-/babel-plugin-react-native-web-0.18.12.tgz#3e9764484492ea612a16b40135b07c2d05b7969d"
   integrity sha512-4djr9G6fMdwQoD6LQ7hOKAm39+y12flWgovAqS1k5O8f42YQ3A1FFMyV5kKfetZuGhZO5BmNmOdRRZQ1TixtDw==
@@ -3850,6 +4663,19 @@ babel-preset-current-node-syntax@^1.0.0:
     "@babel/plugin-syntax-optional-chaining" "^7.8.3"
     "@babel/plugin-syntax-top-level-await" "^7.8.3"
 
+babel-preset-expo@~9.3.0:
+  version "9.3.0"
+  resolved "https://registry.yarnpkg.com/babel-preset-expo/-/babel-preset-expo-9.3.0.tgz#51cb3c6e22126bcc14d17322d2f2dfb418e71222"
+  integrity sha512-cIz+5TVBkcZgtfpTyFPo1peswr2dvQj2VIwdj5vY37/zESsYBHfaZ+u/A11yb1WnuZHcYD/ZoSLNwmWr20jp4Q==
+  dependencies:
+    "@babel/plugin-proposal-decorators" "^7.12.9"
+    "@babel/plugin-proposal-object-rest-spread" "^7.12.13"
+    "@babel/plugin-transform-react-jsx" "^7.12.17"
+    "@babel/preset-env" "^7.20.0"
+    babel-plugin-module-resolver "^4.1.0"
+    babel-plugin-react-native-web "~0.18.10"
+    metro-react-native-babel-preset "0.73.7"
+
 babel-preset-fbjs@^3.4.0:
   version "3.4.0"
   resolved "https://registry.yarnpkg.com/babel-preset-fbjs/-/babel-preset-fbjs-3.4.0.tgz#38a14e5a7a3b285a3f3a86552d650dca5cf6111c"
@@ -3891,12 +4717,12 @@ babel-preset-jest@^27.5.1:
     babel-plugin-jest-hoist "^27.5.1"
     babel-preset-current-node-syntax "^1.0.0"
 
-babel-preset-jest@^29.4.3:
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.4.3.tgz#bb926b66ae253b69c6e3ef87511b8bb5c53c5b52"
-  integrity sha512-gWx6COtSuma6n9bw+8/F+2PCXrIgxV/D1TJFnp6OyBK2cxPWg0K9p/sriNYeifKjpUkMViWQ09DSWtzJQRETsw==
+babel-preset-jest@^29.5.0:
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz#57bc8cc88097af7ff6a5ab59d1cd29d52a5916e2"
+  integrity sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==
   dependencies:
-    babel-plugin-jest-hoist "^29.4.3"
+    babel-plugin-jest-hoist "^29.5.0"
     babel-preset-current-node-syntax "^1.0.0"
 
 babel-preset-react-app@^10.0.1:
@@ -3931,7 +4757,7 @@ base-64@0.1.0, base-64@^0.1.0:
   resolved "https://registry.yarnpkg.com/base-64/-/base-64-0.1.0.tgz#780a99c84e7d600260361511c4877613bf24f6bb"
   integrity sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==
 
-base64-js@^1.1.2, base64-js@^1.3.1, base64-js@^1.5.1:
+base64-js@^1.1.2, base64-js@^1.2.3, base64-js@^1.3.1, base64-js@^1.5.1:
   version "1.5.1"
   resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
   integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
@@ -3954,6 +4780,13 @@ batch@0.6.1:
   resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16"
   integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==
 
+better-opn@~3.0.2:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/better-opn/-/better-opn-3.0.2.tgz#f96f35deaaf8f34144a4102651babcf00d1d8817"
+  integrity sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==
+  dependencies:
+    open "^8.0.4"
+
 better-sqlite3@^7.6.2:
   version "7.6.2"
   resolved "https://registry.yarnpkg.com/better-sqlite3/-/better-sqlite3-7.6.2.tgz#47cd8cad5b9573cace535f950ac321166bc31384"
@@ -3972,7 +4805,7 @@ bfj@^7.0.2:
     hoopy "^0.1.4"
     tryer "^1.0.1"
 
-big-integer@^1.6.51:
+big-integer@1.6.x, big-integer@^1.6.51:
   version "1.6.51"
   resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686"
   integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==
@@ -4008,6 +4841,11 @@ bluebird@^3.5.4, bluebird@^3.5.5:
   resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
   integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
 
+blueimp-md5@^2.10.0:
+  version "2.19.0"
+  resolved "https://registry.yarnpkg.com/blueimp-md5/-/blueimp-md5-2.19.0.tgz#b53feea5498dcb53dc6ec4b823adb84b729c4af0"
+  integrity sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==
+
 body-parser@1.20.1:
   version "1.20.1"
   resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668"
@@ -4026,6 +4864,24 @@ body-parser@1.20.1:
     type-is "~1.6.18"
     unpipe "1.0.0"
 
+body-parser@^1.20.1:
+  version "1.20.2"
+  resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd"
+  integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==
+  dependencies:
+    bytes "3.1.2"
+    content-type "~1.0.5"
+    debug "2.6.9"
+    depd "2.0.0"
+    destroy "1.2.0"
+    http-errors "2.0.0"
+    iconv-lite "0.4.24"
+    on-finished "2.4.1"
+    qs "6.11.0"
+    raw-body "2.5.2"
+    type-is "~1.6.18"
+    unpipe "1.0.0"
+
 bonjour-service@^1.0.11:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.1.0.tgz#424170268d68af26ff83a5c640b95def01803a13"
@@ -4046,6 +4902,27 @@ boolean@^3.1.4:
   resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.2.0.tgz#9e5294af4e98314494cbb17979fa54ca159f116b"
   integrity sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==
 
+bplist-creator@0.1.0:
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/bplist-creator/-/bplist-creator-0.1.0.tgz#018a2d1b587f769e379ef5519103730f8963ba1e"
+  integrity sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg==
+  dependencies:
+    stream-buffers "2.2.x"
+
+bplist-parser@0.3.1:
+  version "0.3.1"
+  resolved "https://registry.yarnpkg.com/bplist-parser/-/bplist-parser-0.3.1.tgz#e1c90b2ca2a9f9474cc72f6862bbf3fee8341fd1"
+  integrity sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA==
+  dependencies:
+    big-integer "1.6.x"
+
+bplist-parser@^0.3.1:
+  version "0.3.2"
+  resolved "https://registry.yarnpkg.com/bplist-parser/-/bplist-parser-0.3.2.tgz#3ac79d67ec52c4c107893e0237eb787cbacbced7"
+  integrity sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ==
+  dependencies:
+    big-integer "1.6.x"
+
 brace-expansion@^1.1.7:
   version "1.1.11"
   resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
@@ -4106,11 +4983,29 @@ bser@2.1.1:
   dependencies:
     node-int64 "^0.4.0"
 
+buffer-alloc-unsafe@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0"
+  integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==
+
+buffer-alloc@^1.1.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec"
+  integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==
+  dependencies:
+    buffer-alloc-unsafe "^1.1.0"
+    buffer-fill "^1.0.0"
+
 buffer-equal-constant-time@1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
   integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==
 
+buffer-fill@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c"
+  integrity sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==
+
 buffer-from@^1.0.0:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
@@ -4142,6 +5037,11 @@ builtin-modules@^3.1.0:
   resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6"
   integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==
 
+builtins@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/builtins/-/builtins-1.0.3.tgz#cb94faeb61c8696451db36534e1422f94f0aee88"
+  integrity sha512-uYBjakWipfaO/bXI7E8rq6kpwHRZK5cNYrUv2OzZSI/FvmdMyXJ2tG9dKcjEC5YHmHpUAwsargWIZNWdxb/bnQ==
+
 bunyan-debug-stream@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/bunyan-debug-stream/-/bunyan-debug-stream-3.1.0.tgz#78309c67ad85cfb8f011155334152c49209dcda8"
@@ -4169,6 +5069,30 @@ bytes@3.1.2, bytes@^3.1.2:
   resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
   integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
 
+cacache@^15.3.0:
+  version "15.3.0"
+  resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.3.0.tgz#dc85380fb2f556fe3dda4c719bfa0ec875a7f1eb"
+  integrity sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==
+  dependencies:
+    "@npmcli/fs" "^1.0.0"
+    "@npmcli/move-file" "^1.0.1"
+    chownr "^2.0.0"
+    fs-minipass "^2.0.0"
+    glob "^7.1.4"
+    infer-owner "^1.0.4"
+    lru-cache "^6.0.0"
+    minipass "^3.1.1"
+    minipass-collect "^1.0.2"
+    minipass-flush "^1.0.5"
+    minipass-pipeline "^1.2.2"
+    mkdirp "^1.0.3"
+    p-map "^4.0.0"
+    promise-inflight "^1.0.1"
+    rimraf "^3.0.2"
+    ssri "^8.0.1"
+    tar "^6.0.2"
+    unique-filename "^1.1.1"
+
 cache-base@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2"
@@ -4255,9 +5179,14 @@ caniuse-api@^3.0.0:
     lodash.uniq "^4.5.0"
 
 caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001426, caniuse-lite@^1.0.30001449:
-  version "1.0.30001458"
-  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001458.tgz#871e35866b4654a7d25eccca86864f411825540c"
-  integrity sha512-lQ1VlUUq5q9ro9X+5gOEyH7i3vm+AYVT1WDCVB69XOZ17KZRhnZ9J0Sqz7wTHQaLBJccNCHq8/Ww5LlOIZbB0w==
+  version "1.0.30001462"
+  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001462.tgz#b2e801e37536d453731286857c8520d3dcee15fe"
+  integrity sha512-PDd20WuOBPiasZ7KbFnmQRyuLE7cFXW2PVd7dmALzbkUXEP46upAuCDm9eY9vho8fgNMGmbAX92QBZHzcnWIqw==
+
+case-anything@^2.1.10:
+  version "2.1.10"
+  resolved "https://registry.yarnpkg.com/case-anything/-/case-anything-2.1.10.tgz#d18a6ca968d54ec3421df71e3e190f3bced23410"
+  integrity sha512-JczJwVrCP0jPKh05McyVsuOg6AYosrB9XWZKbQzXeDAm2ClE/PJE/BcrrQrVyGYH7Jg8V/LDupmyL4kFlVsVFQ==
 
 case-sensitive-paths-webpack-plugin@^2.4.0:
   version "2.4.0"
@@ -4269,7 +5198,7 @@ cborg@^1.6.0:
   resolved "https://registry.yarnpkg.com/cborg/-/cborg-1.10.0.tgz#0fe157961dd47b537ccb84dc9ba681de8b699013"
   integrity sha512-/eM0JCaL99HDHxjySNQJLaolZFVdl6VA0/hEKIoiQPcQzE5LrG5QHdml0HaBt31brgB9dNe1zMr3f8IVrpotRQ==
 
-chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2:
+chalk@^2.0.0, chalk@^2.0.1, chalk@^2.4.1, chalk@^2.4.2:
   version "2.4.2"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
   integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
@@ -4278,6 +5207,14 @@ chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2:
     escape-string-regexp "^1.0.5"
     supports-color "^5.3.0"
 
+chalk@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4"
+  integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==
+  dependencies:
+    ansi-styles "^4.1.0"
+    supports-color "^7.1.0"
+
 chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.2:
   version "4.1.2"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
@@ -4296,6 +5233,11 @@ char-regex@^2.0.0:
   resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-2.0.1.tgz#6dafdb25f9d3349914079f010ba8d0e6ff9cd01e"
   integrity sha512-oSvEeo6ZUD7NepqAat3RqoucZ5SeqLJgOvVIwkafu6IP3V0pO38s/ypdVUmDDK6qIIHNlYHJAKX9E7R7HoKElw==
 
+charenc@0.0.2, charenc@~0.0.1:
+  version "0.0.2"
+  resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
+  integrity sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==
+
 check-types@^11.1.1:
   version "11.2.2"
   resolved "https://registry.yarnpkg.com/check-types/-/check-types-11.2.2.tgz#7afc0b6a860d686885062f2dba888ba5710335b4"
@@ -4330,6 +5272,11 @@ chownr@^1.1.1:
   resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
   integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
 
+chownr@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
+  integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==
+
 chrome-trace-event@^1.0.2:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac"
@@ -4340,7 +5287,7 @@ ci-info@^2.0.0:
   resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46"
   integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==
 
-ci-info@^3.2.0:
+ci-info@^3.2.0, ci-info@^3.3.0:
   version "3.8.0"
   resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.8.0.tgz#81408265a5380c929f0bc665d62256628ce9ef91"
   integrity sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==
@@ -4367,6 +5314,25 @@ clean-css@^5.2.2:
   dependencies:
     source-map "~0.6.0"
 
+clean-stack@^2.0.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
+  integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==
+
+clean-webpack-plugin@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/clean-webpack-plugin/-/clean-webpack-plugin-4.0.0.tgz#72947d4403d452f38ed61a9ff0ada8122aacd729"
+  integrity sha512-WuWE1nyTNAyW5T7oNyys2EN0cfP2fdRxhxnIQWiAp0bMabPdHhoGxM8A6YL2GhqwgrPnnaemVE7nv5XJ2Fhh2w==
+  dependencies:
+    del "^4.1.1"
+
+cli-cursor@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5"
+  integrity sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==
+  dependencies:
+    restore-cursor "^2.0.0"
+
 cli-cursor@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307"
@@ -4374,7 +5340,7 @@ cli-cursor@^3.1.0:
   dependencies:
     restore-cursor "^3.1.0"
 
-cli-spinners@^2.5.0:
+cli-spinners@^2.0.0, cli-spinners@^2.5.0:
   version "2.7.0"
   resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.7.0.tgz#f815fd30b5f9eaac02db604c7a231ed7cb2f797a"
   integrity sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==
@@ -4420,6 +5386,11 @@ clone@^1.0.2:
   resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
   integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==
 
+clone@^2.1.2:
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
+  integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==
+
 co@^4.6.0:
   version "4.6.0"
   resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
@@ -4471,7 +5442,7 @@ color-name@^1.0.0, color-name@^1.1.4, color-name@~1.1.4:
   resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
   integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
 
-color-string@^1.9.0:
+color-string@^1.5.3, color-string@^1.9.0:
   version "1.9.1"
   resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4"
   integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==
@@ -4509,16 +5480,31 @@ combined-stream@^1.0.8:
   dependencies:
     delayed-stream "~1.0.0"
 
-command-exists@^1.2.8:
+command-exists@^1.2.4, command-exists@^1.2.8:
   version "1.2.9"
   resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.9.tgz#c50725af3808c8ab0260fd60b01fbfa25b954f69"
   integrity sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==
 
+commander@2.20.0:
+  version "2.20.0"
+  resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422"
+  integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==
+
 commander@^2.20.0:
   version "2.20.3"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
   integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
 
+commander@^4.0.0:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
+  integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==
+
+commander@^5.1.0:
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae"
+  integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==
+
 commander@^7.2.0:
   version "7.2.0"
   resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7"
@@ -4554,11 +5540,21 @@ commondir@^1.0.1:
   resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
   integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==
 
+compare-versions@^3.4.0:
+  version "3.6.0"
+  resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62"
+  integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==
+
 component-emitter@^1.2.1:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
   integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==
 
+component-type@^1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/component-type/-/component-type-1.2.1.tgz#8a47901700238e4fc32269771230226f24b415a9"
+  integrity sha512-Kgy+2+Uwr75vAi6ChWXgHuLvd+QLD7ssgpaRq2zCvt80ptvAfMc/hijcJxXkBa2wMlEZcJvC2H8Ubo+A9ATHIg==
+
 compressible@~2.0.16:
   version "2.0.18"
   resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba"
@@ -4594,7 +5590,7 @@ connect-history-api-fallback@^2.0.0:
   resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz#647264845251a0daf25b97ce87834cace0f5f1c8"
   integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==
 
-connect@^3.6.5:
+connect@^3.6.5, connect@^3.7.0:
   version "3.7.0"
   resolved "https://registry.yarnpkg.com/connect/-/connect-3.7.0.tgz#5d49348910caa5e07a01800b030d0c35f20484f8"
   integrity sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==
@@ -4611,7 +5607,7 @@ content-disposition@0.5.4:
   dependencies:
     safe-buffer "5.2.1"
 
-content-type@~1.0.4:
+content-type@~1.0.4, content-type@~1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918"
   integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==
@@ -4641,6 +5637,18 @@ copy-descriptor@^0.1.0:
   resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
   integrity sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==
 
+copy-webpack-plugin@^10.2.0:
+  version "10.2.4"
+  resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-10.2.4.tgz#6c854be3fdaae22025da34b9112ccf81c63308fe"
+  integrity sha512-xFVltahqlsRcyyJqQbDY6EYTtyQZF9rf+JPjwHObLdPFMEISqkFkr7mFoVOC6BfYS/dNThyoQKvziugm+OnwBg==
+  dependencies:
+    fast-glob "^3.2.7"
+    glob-parent "^6.0.1"
+    globby "^12.0.2"
+    normalize-path "^3.0.0"
+    schema-utils "^4.0.0"
+    serialize-javascript "^6.0.0"
+
 core-js-compat@^3.25.1:
   version "3.29.0"
   resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.29.0.tgz#1b8d9eb4191ab112022e7f6364b99b65ea52f528"
@@ -4711,6 +5719,11 @@ create-react-class@^15.7.0:
     loose-envify "^1.3.1"
     object-assign "^4.1.1"
 
+crelt@^1.0.0:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.5.tgz#57c0d52af8c859e354bace1883eb2e1eb182bb94"
+  integrity sha512-+BO9wPPi+DWTDcNYhr/W90myha8ptzftZT+LwcmUbbok0rcP/fequmFYCw8NMoH7pkAZQzU78b3kYrlua5a9eA==
+
 cross-fetch@^3.1.5:
   version "3.1.5"
   resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f"
@@ -4726,7 +5739,7 @@ cross-spawn@^4.0.2:
     lru-cache "^4.0.1"
     which "^1.2.9"
 
-cross-spawn@^6.0.0:
+cross-spawn@^6.0.0, cross-spawn@^6.0.5:
   version "6.0.5"
   resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
   integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==
@@ -4746,6 +5759,16 @@ cross-spawn@^7.0.2, cross-spawn@^7.0.3:
     shebang-command "^2.0.0"
     which "^2.0.1"
 
+crypt@0.0.2, crypt@~0.0.1:
+  version "0.0.2"
+  resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
+  integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==
+
+crypto-random-string@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e"
+  integrity sha512-GsVpkFPlycH7/fRR7Dhcmnoii54gV1nz7y4CWyeFS14N+JVBBhY+r8amRHE4BwSYal7BPTDp8isvAlCxyFt3Hg==
+
 crypto-random-string@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"
@@ -4791,7 +5814,7 @@ css-loader@^6.5.1:
     postcss-value-parser "^4.2.0"
     semver "^7.3.8"
 
-css-minimizer-webpack-plugin@^3.2.0:
+css-minimizer-webpack-plugin@^3.2.0, css-minimizer-webpack-plugin@^3.4.1:
   version "3.4.1"
   resolved "https://registry.yarnpkg.com/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.4.1.tgz#ab78f781ced9181992fe7b6e4f3422e76429878f"
   integrity sha512-1u6D71zeIfgngN2XNRJefc/hY7Ybsxd74Jm4qngIXyUEk7fss3VUzuHxLAq/R8NAba4QU9OUSaMZlbpRc7bM4Q==
@@ -4823,7 +5846,7 @@ css-select@^2.0.0:
     domutils "^1.7.0"
     nth-check "^1.0.2"
 
-css-select@^4.1.3:
+css-select@^4.1.3, css-select@^4.2.1:
   version "4.3.0"
   resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.3.0.tgz#db7129b2846662fd8628cfc496abb2b59e41529b"
   integrity sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==
@@ -4942,6 +5965,11 @@ cssom@^0.4.4:
   resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10"
   integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==
 
+cssom@^0.5.0:
+  version "0.5.0"
+  resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.5.0.tgz#d254fa92cd8b6fbd83811b9fbaed34663cc17c36"
+  integrity sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==
+
 cssom@~0.3.6:
   version "0.3.8"
   resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a"
@@ -4959,11 +5987,21 @@ csstype@^3.0.2:
   resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.1.tgz#841b532c45c758ee546a11d5bd7b7b473c8c30b9"
   integrity sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==
 
+dag-map@~1.0.0:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/dag-map/-/dag-map-1.0.2.tgz#e8379f041000ed561fc515475c1ed2c85eece8d7"
+  integrity sha512-+LSAiGFwQ9dRnRdOeaj7g47ZFJcOUPukAP8J3A3fuZ1g9Y44BG+P1sgApjLXTQPOzC4+7S9Wr8kXsfpINM4jpw==
+
 damerau-levenshtein@^1.0.8:
   version "1.0.8"
   resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7"
   integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==
 
+dash-get@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/dash-get/-/dash-get-1.0.2.tgz#4c9e9ad5ef04c4bf9d3c9a451f6f7997298dcc7c"
+  integrity sha512-4FbVrHDwfOASx7uQVxeiCTo7ggSdYZbqs8lH+WU6ViypPlDbe9y6IP5VVUDQBv9DcnyaiPT5XT0UWHgJ64zLeQ==
+
 data-urls@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b"
@@ -4973,6 +6011,15 @@ data-urls@^2.0.0:
     whatwg-mimetype "^2.3.0"
     whatwg-url "^8.0.0"
 
+data-urls@^3.0.2:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-3.0.2.tgz#9cf24a477ae22bcef5cd5f6f0bfbc1d2d3be9143"
+  integrity sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==
+  dependencies:
+    abab "^2.0.6"
+    whatwg-mimetype "^3.0.0"
+    whatwg-url "^11.0.0"
+
 dayjs@^1.8.15:
   version "1.11.7"
   resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.7.tgz#4b296922642f70999544d1144a2c25730fce63e2"
@@ -4985,14 +6032,14 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0:
   dependencies:
     ms "2.0.0"
 
-debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4:
+debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4:
   version "4.3.4"
   resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
   integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
   dependencies:
     ms "2.1.2"
 
-debug@^3.2.7:
+debug@^3.1.0, debug@^3.2.7:
   version "3.2.7"
   resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
   integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
@@ -5009,12 +6056,12 @@ decamelize@^4.0.0:
   resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837"
   integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==
 
-decimal.js@^10.2.1:
+decimal.js@^10.2.1, decimal.js@^10.4.2:
   version "10.4.3"
   resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23"
   integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==
 
-decode-uri-component@^0.2.0:
+decode-uri-component@^0.2.0, decode-uri-component@^0.2.2:
   version "0.2.2"
   resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9"
   integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==
@@ -5074,6 +6121,14 @@ deepmerge@^4.2.2:
   resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.0.tgz#65491893ec47756d44719ae520e0e2609233b59b"
   integrity sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==
 
+default-gateway@^4.2.0:
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b"
+  integrity sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA==
+  dependencies:
+    execa "^1.0.0"
+    ip-regex "^2.1.0"
+
 default-gateway@^6.0.3:
   version "6.0.3"
   resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-6.0.3.tgz#819494c888053bdb743edbf343d6cdf7f2943a71"
@@ -5128,6 +6183,33 @@ defined@^1.0.0:
   resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.1.tgz#c0b9db27bfaffd95d6f61399419b893df0f91ebf"
   integrity sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==
 
+del@^4.1.1:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/del/-/del-4.1.1.tgz#9e8f117222ea44a31ff3a156c049b99052a9f0b4"
+  integrity sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==
+  dependencies:
+    "@types/glob" "^7.1.1"
+    globby "^6.1.0"
+    is-path-cwd "^2.0.0"
+    is-path-in-cwd "^2.0.0"
+    p-map "^2.0.0"
+    pify "^4.0.1"
+    rimraf "^2.6.3"
+
+del@^6.0.0:
+  version "6.1.1"
+  resolved "https://registry.yarnpkg.com/del/-/del-6.1.1.tgz#3b70314f1ec0aa325c6b14eb36b95786671edb7a"
+  integrity sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==
+  dependencies:
+    globby "^11.0.1"
+    graceful-fs "^4.2.4"
+    is-glob "^4.0.1"
+    is-path-cwd "^2.2.0"
+    is-path-inside "^3.0.2"
+    p-map "^4.0.0"
+    rimraf "^3.0.2"
+    slash "^3.0.0"
+
 delay@^5.0.0:
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/delay/-/delay-5.0.0.tgz#137045ef1b96e5071060dd5be60bf9334436bd1d"
@@ -5153,15 +6235,6 @@ depd@~1.1.2:
   resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
   integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==
 
-deprecated-react-native-prop-types@^2.3.0:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/deprecated-react-native-prop-types/-/deprecated-react-native-prop-types-2.3.0.tgz#c10c6ee75ff2b6de94bb127f142b814e6e08d9ab"
-  integrity sha512-pWD0voFtNYxrVqvBMYf5gq3NA2GCpfodS1yNynTPc93AYA/KEMGeWDqqeUB6R2Z9ZofVhks2aeJXiuQqKNpesA==
-  dependencies:
-    "@react-native/normalize-color" "*"
-    invariant "*"
-    prop-types "*"
-
 deprecated-react-native-prop-types@^3.0.1:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/deprecated-react-native-prop-types/-/deprecated-react-native-prop-types-3.0.1.tgz#a275f84cd8519cd1665e8df3c99e9067d57a23ec"
@@ -5171,6 +6244,11 @@ deprecated-react-native-prop-types@^3.0.1:
     invariant "*"
     prop-types "*"
 
+dequal@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/dequal/-/dequal-1.0.1.tgz#dbbf9795ec626e9da8bd68782f4add1d23700d8b"
+  integrity sha512-Fx8jxibzkJX2aJgyfSdLhr9tlRoTnHKrRJuu2XHlAgKioN2j19/Bcbe0d4mFXYZ3+wpE2KVobUVTfDutcD17xQ==
+
 destroy@1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015"
@@ -5209,9 +6287,9 @@ detective@^5.2.1:
     minimist "^1.2.6"
 
 detox@^20.1.2:
-  version "20.3.0"
-  resolved "https://registry.yarnpkg.com/detox/-/detox-20.3.0.tgz#bde67146326b56f047a307fe9f4b5b4e61dbab6f"
-  integrity sha512-1WWejDP3GXToWogs/+/jqTs4ITQ4JaXIVoUrLY8f2Pm/b/fQD2NWqDOraEo2+BqY9ai7KWkppwn4hMbzwqm7sQ==
+  version "20.5.0"
+  resolved "https://registry.yarnpkg.com/detox/-/detox-20.5.0.tgz#70f1aa7ed4a2b652b5787a806e680fafaab2fcb3"
+  integrity sha512-iFDqU5UZ5f1usgRowyiauO83ffMqvN7qFdF5+TVJelfcHTIVHRbZwI/D4MjtAnpuowljfwBhN0tYhTSEOMjCmg==
   dependencies:
     ajv "^8.6.3"
     bunyan "^1.8.12"
@@ -5219,6 +6297,7 @@ detox@^20.1.2:
     caf "^15.0.1"
     chalk "^2.4.2"
     child-process-promise "^2.2.0"
+    execa "^5.1.1"
     find-up "^4.1.0"
     fs-extra "^4.0.2"
     funpermaproxy "^1.1.0"
@@ -5248,20 +6327,15 @@ detox@^20.1.2:
     yargs-unparser "^2.0.0"
 
 did-resolver@^4.0.0:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/did-resolver/-/did-resolver-4.0.1.tgz#11bb3f19ed1c8f53f4af4702912fa9f7852fc305"
-  integrity sha512-eHs2VLKhcANmh08S87PKvOauIAmSOd7nb7AlhNxcvOyDAIGQY1UfbiqI1VOW5IDKvOO6aEWY+5edOt1qrCp1Eg==
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/did-resolver/-/did-resolver-4.1.0.tgz#740852083c4fd5bf9729d528eca5d105aff45eb6"
+  integrity sha512-S6fWHvCXkZg2IhS4RcVHxwuyVejPR7c+a4Go0xbQ9ps5kILa8viiYQgrM4gfTyeTjJ0ekgJH9gk/BawTpmkbZA==
 
 didyoumean@^1.2.2:
   version "1.2.2"
   resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037"
   integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==
 
-diff-sequences@^26.6.2:
-  version "26.6.2"
-  resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1"
-  integrity sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==
-
 diff-sequences@^27.5.1:
   version "27.5.1"
   resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327"
@@ -5360,6 +6434,13 @@ domexception@^2.0.1:
   dependencies:
     webidl-conversions "^5.0.0"
 
+domexception@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/domexception/-/domexception-4.0.0.tgz#4ad1be56ccadc86fc76d033353999a8037d03673"
+  integrity sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==
+  dependencies:
+    webidl-conversions "^7.0.0"
+
 domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1:
   version "4.3.1"
   resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c"
@@ -5467,9 +6548,9 @@ ejs@^3.1.6:
     jake "^10.8.5"
 
 electron-to-chromium@^1.4.284:
-  version "1.4.311"
-  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.311.tgz#953bc9a4767f5ce8ec125f9a1ad8e00e8f67e479"
-  integrity sha512-RoDlZufvrtr2Nx3Yx5MB8jX3aHIxm8nRWPJm3yVvyHmyKaRvn90RjzB6hNnt0AkhS3IInJdyRfQb4mWhPvUjVw==
+  version "1.4.325"
+  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.325.tgz#7b97238a61192d85d055d97f3149832b3617d37b"
+  integrity sha512-K1C03NT4I7BuzsRdCU5RWkgZxtswnKDYM6/eMhkEXqKu4e5T+ck610x3FPzu1y7HVFSiQKZqP16gnJzPpji1TQ==
 
 email-validator@^2.0.4:
   version "2.0.4"
@@ -5531,16 +6612,31 @@ entities@^2.0.0:
   resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
   integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
 
-entities@^4.2.0:
+entities@^4.2.0, entities@^4.4.0:
   version "4.4.0"
   resolved "https://registry.yarnpkg.com/entities/-/entities-4.4.0.tgz#97bdaba170339446495e653cfd2db78962900174"
   integrity sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==
 
+entities@~3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/entities/-/entities-3.0.1.tgz#2b887ca62585e96db3903482d336c1006c3001d4"
+  integrity sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==
+
+env-editor@^0.4.1:
+  version "0.4.2"
+  resolved "https://registry.yarnpkg.com/env-editor/-/env-editor-0.4.2.tgz#4e76568d0bd8f5c2b6d314a9412c8fe9aa3ae861"
+  integrity sha512-ObFo8v4rQJAE59M69QzwloxPZtd33TpYEIjtKD1rrFDcM1Gd7IkDxEBU+HriziN6HSHQnBJi8Dmy+JWkav5HKA==
+
 envinfo@^7.7.2, envinfo@^7.7.3:
   version "7.8.1"
   resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.8.1.tgz#06377e3e5f4d379fea7ac592d5ad8927e0c4d475"
   integrity sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==
 
+eol@^0.9.1:
+  version "0.9.1"
+  resolved "https://registry.yarnpkg.com/eol/-/eol-0.9.1.tgz#f701912f504074be35c6117a5c4ade49cd547acd"
+  integrity sha512-Ds/TEoZjwggRoz/Q2O7SE3i4Jm66mqTDfmdHdq/7DKVk3bro9Q8h6WdXKdPqFLMoqxrDK5SVRzHVPOS6uuGtrg==
+
 error-ex@^1.3.1:
   version "1.3.2"
   resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
@@ -5690,9 +6786,9 @@ escodegen@^2.0.0:
     source-map "~0.6.1"
 
 eslint-config-prettier@^8.5.0:
-  version "8.6.0"
-  resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.6.0.tgz#dec1d29ab728f4fa63061774e1672ac4e363d207"
-  integrity sha512-bAF0eLpLVqP5oEVUFKpMA+NnRFICwn9X8B5jrR9FcqnYBuPbqWEjTEspPWMj5ye6czoSLDweCzSo3Ko7gGrZaA==
+  version "8.7.0"
+  resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.7.0.tgz#f1cc58a8afebc50980bd53475451df146c13182d"
+  integrity sha512-HHVXLSlVUhMSmyW4ZzEuvjpwqamgmlfkutD53cYXLikh4pt/modINRcCIApJ84czDxM4GZInwUrromsDdTImTA==
 
 eslint-config-react-app@^7.0.1:
   version "7.0.1"
@@ -5976,9 +7072,9 @@ esprima@^4.0.0, esprima@^4.0.1, esprima@~4.0.0:
   integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
 
 esquery@^1.4.2:
-  version "1.4.2"
-  resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.2.tgz#c6d3fee05dd665808e2ad870631f221f5617b1d1"
-  integrity sha512-JVSoLdTlTDkmjFmab7H/9SL9qGSyjElT3myyKp7krqjVFQCDLmj1QFaCLRFBszBKI0XVZaiiXvuPIX3ZwHe1Ng==
+  version "1.5.0"
+  resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b"
+  integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==
   dependencies:
     estraverse "^5.1.0"
 
@@ -6034,6 +7130,11 @@ events@^3.2.0, events@^3.3.0:
   resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
   integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
 
+exec-async@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/exec-async/-/exec-async-2.2.0.tgz#c7c5ad2eef3478d38390c6dd3acfe8af0efc8301"
+  integrity sha512-87OpwcEiMia/DeiKFzaQNBNFeN3XkkpYIh9FyOqq5mS2oKv3CBE67PXoEKcr6nodWdXNogTiQ0jE2NGuoffXPw==
+
 execa@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8"
@@ -6047,7 +7148,7 @@ execa@^1.0.0:
     signal-exit "^3.0.0"
     strip-eof "^1.0.0"
 
-execa@^5.0.0:
+execa@^5.0.0, execa@^5.1.1:
   version "5.1.1"
   resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd"
   integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==
@@ -6095,16 +7196,206 @@ expect@^27.5.1:
     jest-matcher-utils "^27.5.1"
     jest-message-util "^27.5.1"
 
-expect@^29.4.3:
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/expect/-/expect-29.4.3.tgz#5e47757316df744fe3b8926c3ae8a3ebdafff7fe"
-  integrity sha512-uC05+Q7eXECFpgDrHdXA4k2rpMyStAYPItEDLyQDo5Ta7fVkJnNA/4zh/OIVkVVNZ1oOK1PipQoyNjuZ6sz6Dg==
+expect@^29.0.0, expect@^29.5.0:
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/expect/-/expect-29.5.0.tgz#68c0509156cb2a0adb8865d413b137eeaae682f7"
+  integrity sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg==
   dependencies:
-    "@jest/expect-utils" "^29.4.3"
+    "@jest/expect-utils" "^29.5.0"
     jest-get-type "^29.4.3"
-    jest-matcher-utils "^29.4.3"
-    jest-message-util "^29.4.3"
-    jest-util "^29.4.3"
+    jest-matcher-utils "^29.5.0"
+    jest-message-util "^29.5.0"
+    jest-util "^29.5.0"
+
+expo-application@~5.1.1:
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/expo-application/-/expo-application-5.1.1.tgz#5206bf0cf89cb0e32d1f5037a0481e5c86b951ab"
+  integrity sha512-aDatTcTTCdTbHw8h4/Tq2ilc6InM5ntF9xWCJdOcnUEcglxxGphVI/lzJKBaBF6mJECA8mEOjpVg2EGxOctTwg==
+
+expo-asset@~8.9.1:
+  version "8.9.1"
+  resolved "https://registry.yarnpkg.com/expo-asset/-/expo-asset-8.9.1.tgz#ecd43d7e8ee879e5023e7ce9fbbd6d011dcaf988"
+  integrity sha512-ugavxA7Scn96TBdeTYQA6xtHktnk0o/0xk7nFkxJKoH/t2cZDFSB05X0BI2/LDZY4iE6xTPOYw4C4mmourWfuA==
+  dependencies:
+    blueimp-md5 "^2.10.0"
+    expo-constants "~14.2.0"
+    expo-file-system "~15.2.0"
+    invariant "^2.2.4"
+    md5-file "^3.2.3"
+    path-browserify "^1.0.0"
+    url-parse "^1.5.9"
+
+expo-camera@~13.2.1:
+  version "13.2.1"
+  resolved "https://registry.yarnpkg.com/expo-camera/-/expo-camera-13.2.1.tgz#bfd1e2248d10a5da43d43a4cc77e378e5acf25bb"
+  integrity sha512-fZdRyF402jJGGmLVlumrLcr5Em9+Y2SO1MIlxLBtHXnybyHbTRMRAbzVapKX1Aryfujqadh+Kl+sdsWYkMuJjg==
+  dependencies:
+    "@koale/useworker" "^4.0.2"
+    invariant "^2.2.4"
+
+expo-constants@~14.2.0, expo-constants@~14.2.1:
+  version "14.2.1"
+  resolved "https://registry.yarnpkg.com/expo-constants/-/expo-constants-14.2.1.tgz#b5b6b8079d2082c31ccf2cbc7cf97a0e83c229c3"
+  integrity sha512-DD5u4QmBds2U7uYo409apV7nX+XjudARcgqe7S9aRFJ/6kyftmuxvk1DpaU4X42Av8z/tfKwEpuxl+vl7HHx/Q==
+  dependencies:
+    "@expo/config" "~8.0.0"
+    uuid "^3.3.2"
+
+expo-dev-client@~2.1.1:
+  version "2.1.5"
+  resolved "https://registry.yarnpkg.com/expo-dev-client/-/expo-dev-client-2.1.5.tgz#a0f0a7e319c09813a001c9df1935adef4eb378d5"
+  integrity sha512-Xcz+4cQhuUgbQ3krEGqjeC6rwVIZsCnOWLHQyuHuiKGtJLJ6CfKHyuCPY53b7c0DI7ThWafKMD3vc78E7ux3TQ==
+  dependencies:
+    expo-dev-launcher "2.1.5"
+    expo-dev-menu "2.1.3"
+    expo-dev-menu-interface "1.1.1"
+    expo-manifests "~0.5.0"
+    expo-updates-interface "~0.9.0"
+
+expo-dev-launcher@2.1.5:
+  version "2.1.5"
+  resolved "https://registry.yarnpkg.com/expo-dev-launcher/-/expo-dev-launcher-2.1.5.tgz#1ed3a407ac8a8f83cd92b0c06e7dcfdfc2dcaf1f"
+  integrity sha512-zwQ21JBEpL1FCTlJrPv3cOaDH9UN7MDPPx8k1j9i4ZxRMdLLYIDmgGiP/oz5dcLf4Z1yi3Ofur42eDYDkKgRlQ==
+  dependencies:
+    expo-dev-menu "2.1.3"
+    resolve-from "^5.0.0"
+    semver "^7.3.5"
+
+expo-dev-menu-interface@1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/expo-dev-menu-interface/-/expo-dev-menu-interface-1.1.1.tgz#8a0d979f62d9a192696f66a77f75d8fab79e604b"
+  integrity sha512-doT+7WrSBnxCcTGZw9QIEZoL+43U4RywbG8XZwbhkcsFWGsh9scp0y/bv3ieFHxRtIdImxbxOoYh7fy1O6g28w==
+
+expo-dev-menu@2.1.3:
+  version "2.1.3"
+  resolved "https://registry.yarnpkg.com/expo-dev-menu/-/expo-dev-menu-2.1.3.tgz#e349d157b284e68c3eebec924c9bdc2f22174dbf"
+  integrity sha512-meQ3irhGNGyx6jKEpHy18WDS7on0iAJSmDnhT3+Jx55Ya+hdIvebF+aHDd4TrE/C5/Hlsn9/Fpm8bFAgmC1xpw==
+  dependencies:
+    expo-dev-menu-interface "1.1.1"
+    semver "^7.3.5"
+
+expo-file-system@~15.2.0, expo-file-system@~15.2.2:
+  version "15.2.2"
+  resolved "https://registry.yarnpkg.com/expo-file-system/-/expo-file-system-15.2.2.tgz#a1ddf8aabf794f93888a146c4f5187e2004683a3"
+  integrity sha512-LFkOLcWwlmnjkURxZ3/0ukS35OswX8iuQknLHRHeyk8mUA8fpRPPelD/a1lS+yclqfqavMJmTXVKM1Nsq5XVMA==
+  dependencies:
+    uuid "^3.4.0"
+
+expo-font@~11.1.1:
+  version "11.1.1"
+  resolved "https://registry.yarnpkg.com/expo-font/-/expo-font-11.1.1.tgz#268eed407e94f6e88083c01b68c357d010748d23"
+  integrity sha512-X+aICqYY69hiiDDtcNrjq8KutHrH2TrHuMqk0Rfq0P7hF6hMd+YefwLBNkvIrqrgmTAuqiLjMUwj2rHLqmgluw==
+  dependencies:
+    fontfaceobserver "^2.1.0"
+
+expo-image-loader@~4.1.0:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/expo-image-loader/-/expo-image-loader-4.1.1.tgz#efadbb17de1861106864820194900f336dd641b6"
+  integrity sha512-ciEHVokU0f6w0eTxdRxLCio6tskMsjxWIoV92+/ZD37qePUJYMfEphPhu1sruyvMBNR8/j5iyOvPFVGTfO8oxA==
+
+expo-image-picker@~14.1.1:
+  version "14.1.1"
+  resolved "https://registry.yarnpkg.com/expo-image-picker/-/expo-image-picker-14.1.1.tgz#181f1348ba6a43df7b87cee4a601d45c79b7c2d7"
+  integrity sha512-SvWtnkLW7jp5Ntvk3lVcRQmhFYja8psmiR7O6P/+7S6f4llt3vaFwb4I3+pUXqJxxpi7BHc2+95qOLf0SFOIag==
+  dependencies:
+    expo-image-loader "~4.1.0"
+
+expo-json-utils@~0.5.0:
+  version "0.5.1"
+  resolved "https://registry.yarnpkg.com/expo-json-utils/-/expo-json-utils-0.5.1.tgz#fcb01050b8aa66592eea2024a48979f2d090c6f9"
+  integrity sha512-Y5boshyf40vPjwxNnOIfacZPNkOymecZRQ1k+TSXlq6gnw5XRsnM5hnP0VLVYhdv8x+9CX6E1fDsDUNvsK38Dg==
+
+expo-keep-awake@~12.0.1:
+  version "12.0.1"
+  resolved "https://registry.yarnpkg.com/expo-keep-awake/-/expo-keep-awake-12.0.1.tgz#19c5ab55391394ded3f6c262b0707c7140658a11"
+  integrity sha512-hqeCnb4033TyuZaXs93zTK7rjVJ3bywXATyMmKmKkLEsH2PKBAl/VmjlCOPQL/2Ncqz6aj7Wo//tjeJTARBD4g==
+
+expo-manifests@~0.5.0:
+  version "0.5.2"
+  resolved "https://registry.yarnpkg.com/expo-manifests/-/expo-manifests-0.5.2.tgz#60f91ad196cd5a37248c28c6f307df806c5a27ad"
+  integrity sha512-WnsTlE2le3pV/B/AJPKTOSjb2K9AT1mPDCfQxTQ/KMCwF95saoXYt2OPF3hxZNaMAV6VIAhXgd5Y6wpcH9ruPQ==
+  dependencies:
+    expo-json-utils "~0.5.0"
+
+expo-media-library@~15.2.1:
+  version "15.2.2"
+  resolved "https://registry.yarnpkg.com/expo-media-library/-/expo-media-library-15.2.2.tgz#71ece905e425f606422cd16019d78b135040e003"
+  integrity sha512-GebBavV9H+m0Qzoy4G7++BWmwUcddLnCee1qGYkCyHT6CvuLNhXUgC3FV9NINEwlii3HGAuCzk1auaEY60SGDA==
+
+expo-modules-autolinking@1.1.2:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/expo-modules-autolinking/-/expo-modules-autolinking-1.1.2.tgz#a81c65c63bd281922410c6d8c3ad6255b6305246"
+  integrity sha512-oOlkAccVnHwwR5ccvF/F/x4Omj9HWzSimMUlIVz0SVGdNBEqTPyn0L/d4uIufhyQbEWvrarqL8o5Yz11wEI0SQ==
+  dependencies:
+    chalk "^4.1.0"
+    commander "^7.2.0"
+    fast-glob "^3.2.5"
+    find-up "^5.0.0"
+    fs-extra "^9.1.0"
+
+expo-modules-core@1.2.4:
+  version "1.2.4"
+  resolved "https://registry.yarnpkg.com/expo-modules-core/-/expo-modules-core-1.2.4.tgz#06c0e92b952ae2d3b3329dbeadb6da4f01965d63"
+  integrity sha512-AV0NCTy9O8xQqpKgX6gvsDzV1ogpCzYpGxqM85Vw1xHsOF51s7Avu7NdNjBPUZOVuDderUXAvd97dWrtefSKcA==
+  dependencies:
+    compare-versions "^3.4.0"
+    invariant "^2.2.4"
+
+expo-pwa@0.0.124:
+  version "0.0.124"
+  resolved "https://registry.yarnpkg.com/expo-pwa/-/expo-pwa-0.0.124.tgz#684e68aea6c7f95864a8cde17a57e223ed017199"
+  integrity sha512-hYvQQhxATNTivWSRc9nrd1WVYJJnBG8P/SVrJ4PPu0pmsS7ZIvWt981IXYG461y9UWnTbXdZEG4UOt0Thak1Gg==
+  dependencies:
+    "@expo/image-utils" "0.3.23"
+    chalk "^4.0.0"
+    commander "2.20.0"
+    update-check "1.5.3"
+
+expo-splash-screen@~0.18.1:
+  version "0.18.1"
+  resolved "https://registry.yarnpkg.com/expo-splash-screen/-/expo-splash-screen-0.18.1.tgz#e090b045a7f8c5d9597b7a96910caa4eae1fcf3b"
+  integrity sha512-1di1kuh14likGUs3fyVZWAqEMxhmdAjpmf9T8Qk5OzUa5oPEMEDYB2e2VprddWnJNBVVe/ojBDSCY8w56/LS0Q==
+  dependencies:
+    "@expo/configure-splash-screen" "^0.6.0"
+    "@expo/prebuild-config" "6.0.0"
+
+expo-status-bar@~1.4.4:
+  version "1.4.4"
+  resolved "https://registry.yarnpkg.com/expo-status-bar/-/expo-status-bar-1.4.4.tgz#6874ccfda5a270d66f123a9f220735a76692d114"
+  integrity sha512-5DV0hIEWgatSC3UgQuAZBoQeaS9CqeWRZ3vzBR9R/+IUD87Adbi4FGhU10nymRqFXOizGsureButGZIXPs7zEA==
+
+expo-updates-interface@~0.9.0:
+  version "0.9.1"
+  resolved "https://registry.yarnpkg.com/expo-updates-interface/-/expo-updates-interface-0.9.1.tgz#e81308d551ed5a4c35c8770ac61434f6ca749610"
+  integrity sha512-wk88LLhseQ7LJvxdN7BTKiryyqALxnrvr+lyHK3/prg76Yy0EGi2Q/oE/rtFyyZ1JmQDRbO/5pdX0EE6QqVQXQ==
+
+expo@~48.0.0-beta.2:
+  version "48.0.6"
+  resolved "https://registry.yarnpkg.com/expo/-/expo-48.0.6.tgz#374e51a6791bc15e998cd66c335d304ea9c4472a"
+  integrity sha512-ylm91v/xYjBBEqFHH+mpNyGijJgFXx4NwgKgHCIEfcAQyTZLXpGCL6teOVzAmHCCVF7EdalLl3If/+n09jOi4g==
+  dependencies:
+    "@babel/runtime" "^7.20.0"
+    "@expo/cli" "0.6.2"
+    "@expo/config" "8.0.2"
+    "@expo/config-plugins" "6.0.1"
+    "@expo/vector-icons" "^13.0.0"
+    babel-preset-expo "~9.3.0"
+    cross-spawn "^6.0.5"
+    expo-application "~5.1.1"
+    expo-asset "~8.9.1"
+    expo-constants "~14.2.1"
+    expo-file-system "~15.2.2"
+    expo-font "~11.1.1"
+    expo-keep-awake "~12.0.1"
+    expo-modules-autolinking "1.1.2"
+    expo-modules-core "1.2.4"
+    fbemitter "^3.0.0"
+    getenv "^1.0.0"
+    invariant "^2.2.4"
+    md5-file "^3.2.3"
+    node-fetch "^2.6.7"
+    pretty-format "^26.5.2"
+    uuid "^3.4.0"
 
 express-async-errors@^3.1.1:
   version "3.1.1"
@@ -6187,7 +7478,7 @@ fast-diff@^1.1.2:
   resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03"
   integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==
 
-fast-glob@^3.2.12, fast-glob@^3.2.9:
+fast-glob@^3.2.12, fast-glob@^3.2.5, fast-glob@^3.2.7, fast-glob@^3.2.9:
   version "3.2.12"
   resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80"
   integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==
@@ -6268,12 +7559,19 @@ fb-watchman@^2.0.0:
   dependencies:
     bser "2.1.1"
 
+fbemitter@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/fbemitter/-/fbemitter-3.0.0.tgz#00b2a1af5411254aab416cd75f9e6289bee4bff3"
+  integrity sha512-KWKaceCwKQU0+HPoop6gn4eOHk50bBv/VxjJtGMfwmJt3D29JpN4H4eisCtIPA+a8GVBam+ldMMpMjJUvpDyHw==
+  dependencies:
+    fbjs "^3.0.0"
+
 fbjs-css-vars@^1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz#216551136ae02fe255932c3ec8775f18e2c078b8"
   integrity sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==
 
-fbjs@^3.0.4:
+fbjs@^3.0.0, fbjs@^3.0.4:
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-3.0.4.tgz#e1871c6bd3083bac71ff2da868ad5067d37716c6"
   integrity sha512-ucV0tDODnGV3JCnnkmoszb5lf4bNpzjv80K41wd4k798Etq+UYD0y0TIfalLjZoKgjive6/adkRnszwapiDgBQ==
@@ -6286,6 +7584,11 @@ fbjs@^3.0.4:
     setimmediate "^1.0.5"
     ua-parser-js "^0.7.30"
 
+fetch-retry@^4.1.1:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/fetch-retry/-/fetch-retry-4.1.1.tgz#fafe0bb22b54f4d0a9c788dff6dd7f8673ca63f3"
+  integrity sha512-e6eB7zN6UBSwGVwrbWVH+gdLnkW9WwHhmq2YDK1Sh30pzx1onRVGBvogTlUeWxwTa+L86NYdo4hFkh7O8ZjSnA==
+
 file-entry-cache@^6.0.1:
   version "6.0.1"
   resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"
@@ -6344,6 +7647,11 @@ fill-range@^7.0.1:
   dependencies:
     to-regex-range "^5.0.1"
 
+filter-obj@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b"
+  integrity sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==
+
 finalhandler@1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d"
@@ -6370,6 +7678,14 @@ finalhandler@1.2.0:
     statuses "2.0.1"
     unpipe "~1.0.0"
 
+find-babel-config@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/find-babel-config/-/find-babel-config-1.2.0.tgz#a9b7b317eb5b9860cda9d54740a8c8337a2283a2"
+  integrity sha512-jB2CHJeqy6a820ssiqwrKMeyC6nNdmrcgkKWJWmpoxpE8RKciYJXCcXRq1h2AzCo5I5BJeN2tkGEO3hLTuePRA==
+  dependencies:
+    json5 "^0.5.1"
+    path-exists "^3.0.0"
+
 find-babel-config@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/find-babel-config/-/find-babel-config-2.0.0.tgz#a8216f825415a839d0f23f4d18338a1cc966f701"
@@ -6411,7 +7727,7 @@ find-up@^4.0.0, find-up@^4.1.0:
     locate-path "^5.0.0"
     path-exists "^4.0.0"
 
-find-up@^5.0.0:
+find-up@^5.0.0, find-up@~5.0.0:
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
   integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
@@ -6419,6 +7735,13 @@ find-up@^5.0.0:
     locate-path "^6.0.0"
     path-exists "^4.0.0"
 
+find-yarn-workspace-root@~2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz#f47fb8d239c900eb78179aa81b66673eac88f7bd"
+  integrity sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==
+  dependencies:
+    micromatch "^4.0.2"
+
 flat-cache@^3.0.4:
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11"
@@ -6438,9 +7761,9 @@ flatted@^3.1.0:
   integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==
 
 flow-parser@0.*:
-  version "0.200.1"
-  resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.200.1.tgz#99a94b35b7d1815716e3db56bb797440ed340716"
-  integrity sha512-N6gxgo0iQx0G2m3aJjg3RLxNLUG3EBYgBN/xDDPGQXSjvqNkTdEd2t1myE36Xi7GndZQWngDP7jf0GvxdL6pRg==
+  version "0.201.0"
+  resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.201.0.tgz#d2005d4dae6fddf60d30f9ae0fb49a13c9c51cfe"
+  integrity sha512-G4oeDNpNGyIrweF9EnoHatncAihMT0tQgV6NMdyM5I7fhrz9Pr13PJ2KLQ673O4wj9KooTdBpeeYHdDNAQoyyw==
 
 flow-parser@^0.185.0:
   version "0.185.2"
@@ -6452,6 +7775,11 @@ follow-redirects@^1.0.0, follow-redirects@^1.14.4, follow-redirects@^1.14.9:
   resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
   integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
 
+fontfaceobserver@^2.1.0:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/fontfaceobserver/-/fontfaceobserver-2.3.0.tgz#5fb392116e75d5024b7ec8e4f2ce92106d1488c8"
+  integrity sha512-6FPvD/IVyT4ZlNe7Wcn5Fb/4ChigpucKYSvD6a+0iMoLn2inpo711eyIcKjmDtE5XNcgAkSH9uN/nfAeZzHEfg==
+
 for-each@^0.3.3:
   version "0.3.3"
   resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e"
@@ -6465,9 +7793,9 @@ for-in@^1.0.2:
   integrity sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==
 
 fork-ts-checker-webpack-plugin@^6.5.0:
-  version "6.5.2"
-  resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.2.tgz#4f67183f2f9eb8ba7df7177ce3cf3e75cdafb340"
-  integrity sha512-m5cUmF30xkZ7h4tWUgTAcEaKmUW7tfyUyTqNNOz7OxWJ0v1VWKTcOvH8FWHUwSjlW/356Ijc9vi3XfcPstpQKA==
+  version "6.5.3"
+  resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.3.tgz#eda2eff6e22476a2688d10661688c47f611b37f3"
+  integrity sha512-SbH/l9ikmMWycd5puHJKTkZJKddF4iRLyW3DeZ08HTI7NGyLS38MXd/KGgeWumQO7YNQbW2u/NtPT2YowbPaGQ==
   dependencies:
     "@babel/code-frame" "^7.8.3"
     "@types/json-schema" "^7.0.5"
@@ -6483,7 +7811,7 @@ fork-ts-checker-webpack-plugin@^6.5.0:
     semver "^7.3.2"
     tapable "^1.0.0"
 
-form-data@^3.0.0:
+form-data@^3.0.0, form-data@^3.0.1:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f"
   integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==
@@ -6518,6 +7846,11 @@ fragment-cache@^0.2.1:
   dependencies:
     map-cache "^0.2.2"
 
+freeport-async@2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/freeport-async/-/freeport-async-2.0.0.tgz#6adf2ec0c629d11abff92836acd04b399135bab4"
+  integrity sha512-K7od3Uw45AJg00XUmy15+Hae2hOcgKcmN3/EF6Y7i01O0gaqiRx8sUSpsb9+BRNL8RPBrhzPsVfy8q9ADlJuWQ==
+
 fresh@0.5.2:
   version "0.5.2"
   resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
@@ -6528,6 +7861,16 @@ fs-constants@^1.0.0:
   resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
   integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
 
+fs-extra@9.0.0:
+  version "9.0.0"
+  resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.0.tgz#b6afc31036e247b2466dc99c29ae797d5d4580a3"
+  integrity sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g==
+  dependencies:
+    at-least-node "^1.0.0"
+    graceful-fs "^4.2.0"
+    jsonfile "^6.0.1"
+    universalify "^1.0.0"
+
 fs-extra@^10.0.0:
   version "10.1.0"
   resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf"
@@ -6546,7 +7889,7 @@ fs-extra@^4.0.2:
     jsonfile "^4.0.0"
     universalify "^0.1.0"
 
-fs-extra@^8.1.0:
+fs-extra@^8.1.0, fs-extra@~8.1.0:
   version "8.1.0"
   resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
   integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==
@@ -6555,7 +7898,7 @@ fs-extra@^8.1.0:
     jsonfile "^4.0.0"
     universalify "^0.1.0"
 
-fs-extra@^9.0.0, fs-extra@^9.0.1:
+fs-extra@^9.0.0, fs-extra@^9.0.1, fs-extra@^9.1.0:
   version "9.1.0"
   resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d"
   integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==
@@ -6565,6 +7908,13 @@ fs-extra@^9.0.0, fs-extra@^9.0.1:
     jsonfile "^6.0.1"
     universalify "^2.0.0"
 
+fs-minipass@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
+  integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==
+  dependencies:
+    minipass "^3.0.0"
+
 fs-monkey@^1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.3.tgz#ae3ac92d53bb328efe0e9a1d9541f6ad8d48e2d3"
@@ -6634,6 +7984,11 @@ get-package-type@^0.1.0:
   resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a"
   integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==
 
+get-port@^3.2.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/get-port/-/get-port-3.2.0.tgz#dd7ce7de187c06c8bf353796ac71e099f0980ebc"
+  integrity sha512-x5UJKlgeUiNT8nyo/AcnwLnZuZNcSjSw0kogRB+Whd1fjjFq4B1hySFxSFWWSn4mIBzg3sRNUDFYc4g5gjPoLg==
+
 get-stream@^4.0.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5"
@@ -6659,6 +8014,11 @@ get-value@^2.0.3, get-value@^2.0.6:
   resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
   integrity sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==
 
+getenv@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/getenv/-/getenv-1.0.0.tgz#874f2e7544fbca53c7a4738f37de8605c3fcfc31"
+  integrity sha512-7yetJWqbS9sbn0vIfliPsFgoXMKn/YMF+Wuiog97x+urnSRRRZ7xB+uVkwGKzRgq9CDFfMQnE9ruL5DHv9c6Xg==
+
 github-from-package@0.0.0:
   version "0.0.0"
   resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce"
@@ -6671,7 +8031,7 @@ glob-parent@^5.1.2, glob-parent@~5.1.2:
   dependencies:
     is-glob "^4.0.1"
 
-glob-parent@^6.0.2:
+glob-parent@^6.0.1, glob-parent@^6.0.2:
   version "6.0.2"
   resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3"
   integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==
@@ -6695,6 +8055,18 @@ glob@7.0.6:
     once "^1.3.0"
     path-is-absolute "^1.0.0"
 
+glob@7.1.6:
+  version "7.1.6"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
+  integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
+  dependencies:
+    fs.realpath "^1.0.0"
+    inflight "^1.0.4"
+    inherits "2"
+    minimatch "^3.0.4"
+    once "^1.3.0"
+    path-is-absolute "^1.0.0"
+
 glob@^6.0.1:
   version "6.0.4"
   resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22"
@@ -6706,7 +8078,7 @@ glob@^6.0.1:
     once "^1.3.0"
     path-is-absolute "^1.0.0"
 
-glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
+glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
   version "7.2.3"
   resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
   integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
@@ -6764,7 +8136,7 @@ globalthis@^1.0.2, globalthis@^1.0.3:
   dependencies:
     define-properties "^1.1.3"
 
-globby@^11.0.4, globby@^11.1.0:
+globby@^11.0.1, globby@^11.0.4, globby@^11.1.0:
   version "11.1.0"
   resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b"
   integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==
@@ -6776,6 +8148,29 @@ globby@^11.0.4, globby@^11.1.0:
     merge2 "^1.4.1"
     slash "^3.0.0"
 
+globby@^12.0.2:
+  version "12.2.0"
+  resolved "https://registry.yarnpkg.com/globby/-/globby-12.2.0.tgz#2ab8046b4fba4ff6eede835b29f678f90e3d3c22"
+  integrity sha512-wiSuFQLZ+urS9x2gGPl1H5drc5twabmm4m2gTR27XDFyjUHJUNsS8o/2aKyIF6IoBaR630atdher0XJ5g6OMmA==
+  dependencies:
+    array-union "^3.0.1"
+    dir-glob "^3.0.1"
+    fast-glob "^3.2.7"
+    ignore "^5.1.9"
+    merge2 "^1.4.1"
+    slash "^4.0.0"
+
+globby@^6.1.0:
+  version "6.1.0"
+  resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c"
+  integrity sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==
+  dependencies:
+    array-union "^1.0.1"
+    glob "^7.0.3"
+    object-assign "^4.0.1"
+    pify "^2.0.0"
+    pinkie-promise "^2.0.0"
+
 gopd@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
@@ -6793,6 +8188,18 @@ grapheme-splitter@^1.0.4:
   resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e"
   integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==
 
+graphql-tag@^2.10.1:
+  version "2.12.6"
+  resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.12.6.tgz#d441a569c1d2537ef10ca3d1633b48725329b5f1"
+  integrity sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==
+  dependencies:
+    tslib "^2.1.0"
+
+graphql@15.8.0:
+  version "15.8.0"
+  resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.8.0.tgz#33410e96b012fa3bdb1091cc99a94769db212b38"
+  integrity sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw==
+
 gzip-size@^6.0.0:
   version "6.0.0"
   resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462"
@@ -6899,7 +8306,7 @@ has@^1.0.3:
   dependencies:
     function-bind "^1.1.1"
 
-he@^1.2.0:
+he@1.2.0, he@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
   integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
@@ -6942,10 +8349,12 @@ hoopy@^0.1.4:
   resolved "https://registry.yarnpkg.com/hoopy/-/hoopy-0.1.4.tgz#609207d661100033a9a9402ad3dea677381c1b1d"
   integrity sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==
 
-hosted-git-info@^2.1.4:
-  version "2.8.9"
-  resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
-  integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
+hosted-git-info@^3.0.2:
+  version "3.0.8"
+  resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-3.0.8.tgz#6e35d4cc87af2c5f816e4cb9ce350ba87a3f370d"
+  integrity sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw==
+  dependencies:
+    lru-cache "^6.0.0"
 
 hpack.js@^2.1.6:
   version "2.1.6"
@@ -6964,6 +8373,13 @@ html-encoding-sniffer@^2.0.1:
   dependencies:
     whatwg-encoding "^1.0.5"
 
+html-encoding-sniffer@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9"
+  integrity sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==
+  dependencies:
+    whatwg-encoding "^2.0.0"
+
 html-entities@^2.1.0, html-entities@^2.3.2:
   version "2.3.3"
   resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.3.3.tgz#117d7626bece327fc8baace8868fa6f5ef856e46"
@@ -7058,6 +8474,15 @@ http-proxy-agent@^4.0.1:
     agent-base "6"
     debug "4"
 
+http-proxy-agent@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43"
+  integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==
+  dependencies:
+    "@tootallnate/once" "2"
+    agent-base "6"
+    debug "4"
+
 http-proxy-middleware@^2.0.3:
   version "2.0.6"
   resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz#e1a4dd6979572c7ab5a4e4b55095d1f32a74963f"
@@ -7088,7 +8513,7 @@ http-terminator@^3.2.0:
     roarr "^7.0.4"
     type-fest "^2.3.3"
 
-https-proxy-agent@^5.0.0:
+https-proxy-agent@^5.0.0, https-proxy-agent@^5.0.1:
   version "5.0.1"
   resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6"
   integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==
@@ -7118,7 +8543,7 @@ iconv-lite@0.4.24:
   dependencies:
     safer-buffer ">= 2.1.2 < 3"
 
-iconv-lite@^0.6.3:
+iconv-lite@0.6.3, iconv-lite@^0.6.3:
   version "0.6.3"
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
   integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
@@ -7147,7 +8572,7 @@ ieee754@^1.1.13, ieee754@^1.2.1:
   resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
   integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
 
-ignore@^5.0.5, ignore@^5.2.0:
+ignore@^5.0.5, ignore@^5.1.9, ignore@^5.2.0:
   version "5.2.4"
   resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324"
   integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==
@@ -7196,6 +8621,11 @@ indent-string@^4.0.0:
   resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
   integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==
 
+infer-owner@^1.0.4:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467"
+  integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==
+
 inflight@^1.0.4:
   version "1.0.6"
   resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
@@ -7227,6 +8657,14 @@ inline-style-prefixer@^6.0.1:
     css-in-js-utils "^3.1.0"
     fast-loops "^1.1.3"
 
+internal-ip@4.3.0:
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907"
+  integrity sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==
+  dependencies:
+    default-gateway "^4.2.0"
+    ipaddr.js "^1.9.0"
+
 internal-slot@^1.0.3, internal-slot@^1.0.4:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.5.tgz#f2a2ee21f668f8627a4667f309dc0f4fb6674986"
@@ -7248,12 +8686,17 @@ invariant@*, invariant@2.2.4, invariant@^2.2.4:
   dependencies:
     loose-envify "^1.0.0"
 
+ip-regex@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
+  integrity sha512-58yWmlHpp7VYfcdTwMTvwMmqx/Elfxjd9RXTDyMsbL7lLWmhMylLEqiYVLKuLzOZqVgiWXD9MfR62Vv89VRxkw==
+
 ip@^1.1.5:
   version "1.1.8"
   resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.8.tgz#ae05948f6b075435ed3307acce04629da8cdbf48"
   integrity sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==
 
-ipaddr.js@1.9.1:
+ipaddr.js@1.9.1, ipaddr.js@^1.9.0:
   version "1.9.1"
   resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
   integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
@@ -7286,12 +8729,12 @@ is-arguments@^1.1.1:
     has-tostringtag "^1.0.0"
 
 is-array-buffer@^3.0.1:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.1.tgz#deb1db4fcae48308d54ef2442706c0393997052a"
-  integrity sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz#f2653ced8412081638ecb0ebbd0c41c6e0aecbbe"
+  integrity sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==
   dependencies:
     call-bind "^1.0.2"
-    get-intrinsic "^1.1.3"
+    get-intrinsic "^1.2.0"
     is-typed-array "^1.1.10"
 
 is-arrayish@^0.2.1:
@@ -7326,7 +8769,7 @@ is-boolean-object@^1.1.0:
     call-bind "^1.0.2"
     has-tostringtag "^1.0.0"
 
-is-buffer@^1.1.5:
+is-buffer@^1.1.5, is-buffer@~1.1.1, is-buffer@~1.1.6:
   version "1.1.6"
   resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
   integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
@@ -7397,13 +8840,18 @@ is-extendable@^0.1.0, is-extendable@^0.1.1:
   resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
   integrity sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==
 
-is-extendable@^1.0.1:
+is-extendable@^1.0.0, is-extendable@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4"
   integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==
   dependencies:
     is-plain-object "^2.0.4"
 
+is-extglob@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0"
+  integrity sha512-7Q+VbVafe6x2T+Tu6NcOf6sRklazEPmBoB3IWk3WdGZM2iGUwU/Oe3Wtq5lSEkDTTlpp8yx+5t4pzO/i9Ty1ww==
+
 is-extglob@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
@@ -7424,6 +8872,13 @@ is-generator-fn@^2.0.0:
   resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118"
   integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==
 
+is-glob@^2.0.0:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863"
+  integrity sha512-a1dBeB19NXsf/E0+FHqkagizel/LQw2DjSQpvQrj3zT+jYPpaUCryPnrQajXKFLCMuf4I6FhRpaGtw4lPrG6Eg==
+  dependencies:
+    is-extglob "^1.0.0"
+
 is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
   version "4.0.3"
   resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
@@ -7436,6 +8891,13 @@ is-interactive@^1.0.0:
   resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e"
   integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==
 
+is-invalid-path@^0.1.0:
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/is-invalid-path/-/is-invalid-path-0.1.0.tgz#307a855b3cf1a938b44ea70d2c61106053714f34"
+  integrity sha512-aZMG0T3F34mTg4eTdszcGXx54oiZ4NtHSft3hWNJMGJXUUqdIj3cOZuHcU0nCWWcY3jd7yRe/3AEm3vSNTpBGQ==
+  dependencies:
+    is-glob "^2.0.0"
+
 is-map@^2.0.1, is-map@^2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127"
@@ -7475,7 +8937,26 @@ is-obj@^1.0.1:
   resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
   integrity sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==
 
-is-path-inside@^3.0.3:
+is-path-cwd@^2.0.0, is-path-cwd@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb"
+  integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==
+
+is-path-in-cwd@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz#bfe2dca26c69f397265a4009963602935a053acb"
+  integrity sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==
+  dependencies:
+    is-path-inside "^2.1.0"
+
+is-path-inside@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-2.1.0.tgz#7c9810587d659a40d27bcdb4d5616eab059494b2"
+  integrity sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==
+  dependencies:
+    path-is-inside "^1.0.2"
+
+is-path-inside@^3.0.2, is-path-inside@^3.0.3:
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
   integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
@@ -7577,6 +9058,13 @@ is-unicode-supported@^0.1.0:
   resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7"
   integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==
 
+is-valid-path@^0.1.1:
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/is-valid-path/-/is-valid-path-0.1.1.tgz#110f9ff74c37f663e1ec7915eb451f2db93ac9df"
+  integrity sha512-+kwPrVDu9Ms03L90Qaml+79+6DZHqHyRoANI6IsZJ/g8frhnfchDOBCa0RbQ6/kdHt5CS5OeIEyrYznNuVN+8A==
+  dependencies:
+    is-invalid-path "^0.1.0"
+
 is-weakmap@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2"
@@ -7607,7 +9095,7 @@ is-wsl@^1.1.0:
   resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
   integrity sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==
 
-is-wsl@^2.2.0:
+is-wsl@^2.0.0, is-wsl@^2.1.1, is-wsl@^2.2.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
   integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
@@ -7707,10 +9195,10 @@ jest-changed-files@^27.5.1:
     execa "^5.0.0"
     throat "^6.0.1"
 
-jest-changed-files@^29.4.3:
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.4.3.tgz#7961fe32536b9b6d5c28dfa0abcfab31abcf50a7"
-  integrity sha512-Vn5cLuWuwmi2GNNbokPOEcvrXGSGrqVnPEZV7rC6P7ck07Dyw9RFnvWglnupSh+hGys0ajGtw/bc2ZgweljQoQ==
+jest-changed-files@^29.5.0:
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.5.0.tgz#e88786dca8bf2aa899ec4af7644e16d9dcf9b23e"
+  integrity sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==
   dependencies:
     execa "^5.0.0"
     p-limit "^3.1.0"
@@ -7740,28 +9228,29 @@ jest-circus@^27.5.1:
     stack-utils "^2.0.3"
     throat "^6.0.1"
 
-jest-circus@^29.4.3:
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.4.3.tgz#fff7be1cf5f06224dd36a857d52a9efeb005ba04"
-  integrity sha512-Vw/bVvcexmdJ7MLmgdT3ZjkJ3LKu8IlpefYokxiqoZy6OCQ2VAm6Vk3t/qHiAGUXbdbJKJWnc8gH3ypTbB/OBw==
+jest-circus@^29.5.0:
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.5.0.tgz#b5926989449e75bff0d59944bae083c9d7fb7317"
+  integrity sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA==
   dependencies:
-    "@jest/environment" "^29.4.3"
-    "@jest/expect" "^29.4.3"
-    "@jest/test-result" "^29.4.3"
-    "@jest/types" "^29.4.3"
+    "@jest/environment" "^29.5.0"
+    "@jest/expect" "^29.5.0"
+    "@jest/test-result" "^29.5.0"
+    "@jest/types" "^29.5.0"
     "@types/node" "*"
     chalk "^4.0.0"
     co "^4.6.0"
     dedent "^0.7.0"
     is-generator-fn "^2.0.0"
-    jest-each "^29.4.3"
-    jest-matcher-utils "^29.4.3"
-    jest-message-util "^29.4.3"
-    jest-runtime "^29.4.3"
-    jest-snapshot "^29.4.3"
-    jest-util "^29.4.3"
+    jest-each "^29.5.0"
+    jest-matcher-utils "^29.5.0"
+    jest-message-util "^29.5.0"
+    jest-runtime "^29.5.0"
+    jest-snapshot "^29.5.0"
+    jest-util "^29.5.0"
     p-limit "^3.1.0"
-    pretty-format "^29.4.3"
+    pretty-format "^29.5.0"
+    pure-rand "^6.0.0"
     slash "^3.0.0"
     stack-utils "^2.0.3"
 
@@ -7783,21 +9272,21 @@ jest-cli@^27.5.1:
     prompts "^2.0.1"
     yargs "^16.2.0"
 
-jest-cli@^29.4.3:
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.4.3.tgz#fe31fdd0c90c765f392b8b7c97e4845071cd2163"
-  integrity sha512-PiiAPuFNfWWolCE6t3ZrDXQc6OsAuM3/tVW0u27UWc1KE+n/HSn5dSE6B2juqN7WP+PP0jAcnKtGmI4u8GMYCg==
+jest-cli@^29.5.0:
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.5.0.tgz#b34c20a6d35968f3ee47a7437ff8e53e086b4a67"
+  integrity sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==
   dependencies:
-    "@jest/core" "^29.4.3"
-    "@jest/test-result" "^29.4.3"
-    "@jest/types" "^29.4.3"
+    "@jest/core" "^29.5.0"
+    "@jest/test-result" "^29.5.0"
+    "@jest/types" "^29.5.0"
     chalk "^4.0.0"
     exit "^0.1.2"
     graceful-fs "^4.2.9"
     import-local "^3.0.2"
-    jest-config "^29.4.3"
-    jest-util "^29.4.3"
-    jest-validate "^29.4.3"
+    jest-config "^29.5.0"
+    jest-util "^29.5.0"
+    jest-validate "^29.5.0"
     prompts "^2.0.1"
     yargs "^17.3.1"
 
@@ -7831,44 +9320,34 @@ jest-config@^27.5.1:
     slash "^3.0.0"
     strip-json-comments "^3.1.1"
 
-jest-config@^29.4.3:
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.4.3.tgz#fca9cdfe6298ae6d04beef1624064d455347c978"
-  integrity sha512-eCIpqhGnIjdUCXGtLhz4gdDoxKSWXKjzNcc5r+0S1GKOp2fwOipx5mRcwa9GB/ArsxJ1jlj2lmlD9bZAsBxaWQ==
+jest-config@^29.5.0:
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.5.0.tgz#3cc972faec8c8aaea9ae158c694541b79f3748da"
+  integrity sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==
   dependencies:
     "@babel/core" "^7.11.6"
-    "@jest/test-sequencer" "^29.4.3"
-    "@jest/types" "^29.4.3"
-    babel-jest "^29.4.3"
+    "@jest/test-sequencer" "^29.5.0"
+    "@jest/types" "^29.5.0"
+    babel-jest "^29.5.0"
     chalk "^4.0.0"
     ci-info "^3.2.0"
     deepmerge "^4.2.2"
     glob "^7.1.3"
     graceful-fs "^4.2.9"
-    jest-circus "^29.4.3"
-    jest-environment-node "^29.4.3"
+    jest-circus "^29.5.0"
+    jest-environment-node "^29.5.0"
     jest-get-type "^29.4.3"
     jest-regex-util "^29.4.3"
-    jest-resolve "^29.4.3"
-    jest-runner "^29.4.3"
-    jest-util "^29.4.3"
-    jest-validate "^29.4.3"
+    jest-resolve "^29.5.0"
+    jest-runner "^29.5.0"
+    jest-util "^29.5.0"
+    jest-validate "^29.5.0"
     micromatch "^4.0.4"
     parse-json "^5.2.0"
-    pretty-format "^29.4.3"
+    pretty-format "^29.5.0"
     slash "^3.0.0"
     strip-json-comments "^3.1.1"
 
-jest-diff@^26.0.0:
-  version "26.6.2"
-  resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.6.2.tgz#1aa7468b52c3a68d7d5c5fdcdfcd5e49bd164394"
-  integrity sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==
-  dependencies:
-    chalk "^4.0.0"
-    diff-sequences "^26.6.2"
-    jest-get-type "^26.3.0"
-    pretty-format "^26.6.2"
-
 jest-diff@^27.5.1:
   version "27.5.1"
   resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.5.1.tgz#a07f5011ac9e6643cf8a95a462b7b1ecf6680def"
@@ -7879,15 +9358,15 @@ jest-diff@^27.5.1:
     jest-get-type "^27.5.1"
     pretty-format "^27.5.1"
 
-jest-diff@^29.0.1, jest-diff@^29.4.3:
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.4.3.tgz#42f4eb34d0bf8c0fb08b0501069b87e8e84df347"
-  integrity sha512-YB+ocenx7FZ3T5O9lMVMeLYV4265socJKtkwgk/6YUz/VsEzYDkiMuMhWzZmxm3wDRQvayJu/PjkjjSkjoHsCA==
+jest-diff@^29.0.1, jest-diff@^29.5.0:
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.5.0.tgz#e0d83a58eb5451dcc1fa61b1c3ee4e8f5a290d63"
+  integrity sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==
   dependencies:
     chalk "^4.0.0"
     diff-sequences "^29.4.3"
     jest-get-type "^29.4.3"
-    pretty-format "^29.4.3"
+    pretty-format "^29.5.0"
 
 jest-docblock@^27.5.1:
   version "27.5.1"
@@ -7914,16 +9393,16 @@ jest-each@^27.5.1:
     jest-util "^27.5.1"
     pretty-format "^27.5.1"
 
-jest-each@^29.4.3:
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.4.3.tgz#a434c199a2f6151c5e3dc80b2d54586bdaa72819"
-  integrity sha512-1ElHNAnKcbJb/b+L+7j0/w7bDvljw4gTv1wL9fYOczeJrbTbkMGQ5iQPFJ3eFQH19VPTx1IyfePdqSpePKss7Q==
+jest-each@^29.5.0:
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.5.0.tgz#fc6e7014f83eac68e22b7195598de8554c2e5c06"
+  integrity sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA==
   dependencies:
-    "@jest/types" "^29.4.3"
+    "@jest/types" "^29.5.0"
     chalk "^4.0.0"
     jest-get-type "^29.4.3"
-    jest-util "^29.4.3"
-    pretty-format "^29.4.3"
+    jest-util "^29.5.0"
+    pretty-format "^29.5.0"
 
 jest-environment-jsdom@^27.5.1:
   version "27.5.1"
@@ -7938,6 +9417,20 @@ jest-environment-jsdom@^27.5.1:
     jest-util "^27.5.1"
     jsdom "^16.6.0"
 
+jest-environment-jsdom@^29.2.1:
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-29.5.0.tgz#cfe86ebaf1453f3297b5ff3470fbe94739c960cb"
+  integrity sha512-/KG8yEK4aN8ak56yFVdqFDzKNHgF4BAymCx2LbPNPsUshUlfAl0eX402Xm1pt+eoG9SLZEUVifqXtX8SK74KCw==
+  dependencies:
+    "@jest/environment" "^29.5.0"
+    "@jest/fake-timers" "^29.5.0"
+    "@jest/types" "^29.5.0"
+    "@types/jsdom" "^20.0.0"
+    "@types/node" "*"
+    jest-mock "^29.5.0"
+    jest-util "^29.5.0"
+    jsdom "^20.0.0"
+
 jest-environment-node@^27.5.1:
   version "27.5.1"
   resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-27.5.1.tgz#dedc2cfe52fab6b8f5714b4808aefa85357a365e"
@@ -7950,17 +9443,33 @@ jest-environment-node@^27.5.1:
     jest-mock "^27.5.1"
     jest-util "^27.5.1"
 
-jest-environment-node@^29.2.1, jest-environment-node@^29.4.3:
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.4.3.tgz#579c4132af478befc1889ddc43c2413a9cdbe014"
-  integrity sha512-gAiEnSKF104fsGDXNkwk49jD/0N0Bqu2K9+aMQXA6avzsA9H3Fiv1PW2D+gzbOSR705bWd2wJZRFEFpV0tXISg==
+jest-environment-node@^29.2.1, jest-environment-node@^29.5.0:
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.5.0.tgz#f17219d0f0cc0e68e0727c58b792c040e332c967"
+  integrity sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw==
   dependencies:
-    "@jest/environment" "^29.4.3"
-    "@jest/fake-timers" "^29.4.3"
-    "@jest/types" "^29.4.3"
+    "@jest/environment" "^29.5.0"
+    "@jest/fake-timers" "^29.5.0"
+    "@jest/types" "^29.5.0"
     "@types/node" "*"
-    jest-mock "^29.4.3"
-    jest-util "^29.4.3"
+    jest-mock "^29.5.0"
+    jest-util "^29.5.0"
+
+jest-expo@^48.0.0-beta.2:
+  version "48.0.2"
+  resolved "https://registry.yarnpkg.com/jest-expo/-/jest-expo-48.0.2.tgz#eedab424e29e9bec2cf17a2fe1a653096ec82b04"
+  integrity sha512-hxppv3I3/WgtswladHpPlcEHCv+5/6OG8nOuR3VqtS0h7ZJYuyQCMpXbsKZiA4R/sT4fHS0BUj9BBsdhrk/zXg==
+  dependencies:
+    "@expo/config" "~8.0.0"
+    "@jest/create-cache-key-function" "^29.2.1"
+    babel-jest "^29.2.1"
+    find-up "^5.0.0"
+    jest-environment-jsdom "^29.2.1"
+    jest-watch-select-projects "^2.0.0"
+    jest-watch-typeahead "2.2.1"
+    json5 "^2.1.0"
+    lodash "^4.17.19"
+    react-test-renderer "18.2.0"
 
 jest-get-type@^26.3.0:
   version "26.3.0"
@@ -7997,20 +9506,20 @@ jest-haste-map@^27.5.1:
   optionalDependencies:
     fsevents "^2.3.2"
 
-jest-haste-map@^29.4.3:
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.4.3.tgz#085a44283269e7ace0645c63a57af0d2af6942e2"
-  integrity sha512-eZIgAS8tvm5IZMtKlR8Y+feEOMfo2pSQkmNbufdbMzMSn9nitgGxF1waM/+LbryO3OkMcKS98SUb+j/cQxp/vQ==
+jest-haste-map@^29.5.0:
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.5.0.tgz#69bd67dc9012d6e2723f20a945099e972b2e94de"
+  integrity sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA==
   dependencies:
-    "@jest/types" "^29.4.3"
+    "@jest/types" "^29.5.0"
     "@types/graceful-fs" "^4.1.3"
     "@types/node" "*"
     anymatch "^3.0.3"
     fb-watchman "^2.0.0"
     graceful-fs "^4.2.9"
     jest-regex-util "^29.4.3"
-    jest-util "^29.4.3"
-    jest-worker "^29.4.3"
+    jest-util "^29.5.0"
+    jest-worker "^29.5.0"
     micromatch "^4.0.4"
     walker "^1.0.8"
   optionalDependencies:
@@ -8057,13 +9566,13 @@ jest-leak-detector@^27.5.1:
     jest-get-type "^27.5.1"
     pretty-format "^27.5.1"
 
-jest-leak-detector@^29.4.3:
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.4.3.tgz#2b35191d6b35aa0256e63a9b79b0f949249cf23a"
-  integrity sha512-9yw4VC1v2NspMMeV3daQ1yXPNxMgCzwq9BocCwYrRgXe4uaEJPAN0ZK37nFBhcy3cUwEVstFecFLaTHpF7NiGA==
+jest-leak-detector@^29.5.0:
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz#cf4bdea9615c72bac4a3a7ba7e7930f9c0610c8c"
+  integrity sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow==
   dependencies:
     jest-get-type "^29.4.3"
-    pretty-format "^29.4.3"
+    pretty-format "^29.5.0"
 
 jest-matcher-utils@^27.5.1:
   version "27.5.1"
@@ -8075,15 +9584,15 @@ jest-matcher-utils@^27.5.1:
     jest-get-type "^27.5.1"
     pretty-format "^27.5.1"
 
-jest-matcher-utils@^29.0.1, jest-matcher-utils@^29.4.3:
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.4.3.tgz#ea68ebc0568aebea4c4213b99f169ff786df96a0"
-  integrity sha512-TTciiXEONycZ03h6R6pYiZlSkvYgT0l8aa49z/DLSGYjex4orMUcafuLXYyyEDWB1RKglq00jzwY00Ei7yFNVg==
+jest-matcher-utils@^29.0.1, jest-matcher-utils@^29.5.0:
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz#d957af7f8c0692c5453666705621ad4abc2c59c5"
+  integrity sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw==
   dependencies:
     chalk "^4.0.0"
-    jest-diff "^29.4.3"
+    jest-diff "^29.5.0"
     jest-get-type "^29.4.3"
-    pretty-format "^29.4.3"
+    pretty-format "^29.5.0"
 
 jest-message-util@^27.5.1:
   version "27.5.1"
@@ -8115,18 +9624,18 @@ jest-message-util@^28.1.3:
     slash "^3.0.0"
     stack-utils "^2.0.3"
 
-jest-message-util@^29.4.3:
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.4.3.tgz#65b5280c0fdc9419503b49d4f48d4999d481cb5b"
-  integrity sha512-1Y8Zd4ZCN7o/QnWdMmT76If8LuDv23Z1DRovBj/vcSFNlGCJGoO8D1nJDw1AdyAGUk0myDLFGN5RbNeJyCRGCw==
+jest-message-util@^29.5.0:
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.5.0.tgz#1f776cac3aca332ab8dd2e3b41625435085c900e"
+  integrity sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==
   dependencies:
     "@babel/code-frame" "^7.12.13"
-    "@jest/types" "^29.4.3"
+    "@jest/types" "^29.5.0"
     "@types/stack-utils" "^2.0.0"
     chalk "^4.0.0"
     graceful-fs "^4.2.9"
     micromatch "^4.0.4"
-    pretty-format "^29.4.3"
+    pretty-format "^29.5.0"
     slash "^3.0.0"
     stack-utils "^2.0.3"
 
@@ -8138,14 +9647,14 @@ jest-mock@^27.5.1:
     "@jest/types" "^27.5.1"
     "@types/node" "*"
 
-jest-mock@^29.4.3:
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.4.3.tgz#23d84a20a74cdfff0510fdbeefb841ed57b0fe7e"
-  integrity sha512-LjFgMg+xed9BdkPMyIJh+r3KeHt1klXPJYBULXVVAkbTaaKjPX1o1uVCAZADMEp/kOxGTwy/Ot8XbvgItOrHEg==
+jest-mock@^29.5.0:
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.5.0.tgz#26e2172bcc71d8b0195081ff1f146ac7e1518aed"
+  integrity sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw==
   dependencies:
-    "@jest/types" "^29.4.3"
+    "@jest/types" "^29.5.0"
     "@types/node" "*"
-    jest-util "^29.4.3"
+    jest-util "^29.5.0"
 
 jest-pnp-resolver@^1.2.2:
   version "1.2.3"
@@ -8162,7 +9671,7 @@ jest-regex-util@^28.0.0:
   resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-28.0.2.tgz#afdc377a3b25fb6e80825adcf76c854e5bf47ead"
   integrity sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==
 
-jest-regex-util@^29.4.3:
+jest-regex-util@^29.0.0, jest-regex-util@^29.4.3:
   version "29.4.3"
   resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.4.3.tgz#a42616141e0cae052cfa32c169945d00c0aa0bb8"
   integrity sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==
@@ -8176,13 +9685,13 @@ jest-resolve-dependencies@^27.5.1:
     jest-regex-util "^27.5.1"
     jest-snapshot "^27.5.1"
 
-jest-resolve-dependencies@^29.4.3:
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.4.3.tgz#9ad7f23839a6d88cef91416bda9393a6e9fd1da5"
-  integrity sha512-uvKMZAQ3nmXLH7O8WAOhS5l0iWyT3WmnJBdmIHiV5tBbdaDZ1wqtNX04FONGoaFvSOSHBJxnwAVnSn1WHdGVaw==
+jest-resolve-dependencies@^29.5.0:
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz#f0ea29955996f49788bf70996052aa98e7befee4"
+  integrity sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg==
   dependencies:
     jest-regex-util "^29.4.3"
-    jest-snapshot "^29.4.3"
+    jest-snapshot "^29.5.0"
 
 jest-resolve@^27.4.2, jest-resolve@^27.5.1:
   version "27.5.1"
@@ -8200,17 +9709,17 @@ jest-resolve@^27.4.2, jest-resolve@^27.5.1:
     resolve.exports "^1.1.0"
     slash "^3.0.0"
 
-jest-resolve@^29.4.3:
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.4.3.tgz#3c5b5c984fa8a763edf9b3639700e1c7900538e2"
-  integrity sha512-GPokE1tzguRyT7dkxBim4wSx6E45S3bOQ7ZdKEG+Qj0Oac9+6AwJPCk0TZh5Vu0xzeX4afpb+eDmgbmZFFwpOw==
+jest-resolve@^29.5.0:
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.5.0.tgz#b053cc95ad1d5f6327f0ac8aae9f98795475ecdc"
+  integrity sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w==
   dependencies:
     chalk "^4.0.0"
     graceful-fs "^4.2.9"
-    jest-haste-map "^29.4.3"
+    jest-haste-map "^29.5.0"
     jest-pnp-resolver "^1.2.2"
-    jest-util "^29.4.3"
-    jest-validate "^29.4.3"
+    jest-util "^29.5.0"
+    jest-validate "^29.5.0"
     resolve "^1.20.0"
     resolve.exports "^2.0.0"
     slash "^3.0.0"
@@ -8242,30 +9751,30 @@ jest-runner@^27.5.1:
     source-map-support "^0.5.6"
     throat "^6.0.1"
 
-jest-runner@^29.4.3:
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.4.3.tgz#68dc82c68645eda12bea42b5beece6527d7c1e5e"
-  integrity sha512-GWPTEiGmtHZv1KKeWlTX9SIFuK19uLXlRQU43ceOQ2hIfA5yPEJC7AMkvFKpdCHx6pNEdOD+2+8zbniEi3v3gA==
-  dependencies:
-    "@jest/console" "^29.4.3"
-    "@jest/environment" "^29.4.3"
-    "@jest/test-result" "^29.4.3"
-    "@jest/transform" "^29.4.3"
-    "@jest/types" "^29.4.3"
+jest-runner@^29.5.0:
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.5.0.tgz#6a57c282eb0ef749778d444c1d758c6a7693b6f8"
+  integrity sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ==
+  dependencies:
+    "@jest/console" "^29.5.0"
+    "@jest/environment" "^29.5.0"
+    "@jest/test-result" "^29.5.0"
+    "@jest/transform" "^29.5.0"
+    "@jest/types" "^29.5.0"
     "@types/node" "*"
     chalk "^4.0.0"
     emittery "^0.13.1"
     graceful-fs "^4.2.9"
     jest-docblock "^29.4.3"
-    jest-environment-node "^29.4.3"
-    jest-haste-map "^29.4.3"
-    jest-leak-detector "^29.4.3"
-    jest-message-util "^29.4.3"
-    jest-resolve "^29.4.3"
-    jest-runtime "^29.4.3"
-    jest-util "^29.4.3"
-    jest-watcher "^29.4.3"
-    jest-worker "^29.4.3"
+    jest-environment-node "^29.5.0"
+    jest-haste-map "^29.5.0"
+    jest-leak-detector "^29.5.0"
+    jest-message-util "^29.5.0"
+    jest-resolve "^29.5.0"
+    jest-runtime "^29.5.0"
+    jest-util "^29.5.0"
+    jest-watcher "^29.5.0"
+    jest-worker "^29.5.0"
     p-limit "^3.1.0"
     source-map-support "0.5.13"
 
@@ -8297,31 +9806,31 @@ jest-runtime@^27.5.1:
     slash "^3.0.0"
     strip-bom "^4.0.0"
 
-jest-runtime@^29.4.3:
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.4.3.tgz#f25db9874dcf35a3ab27fdaabca426666cc745bf"
-  integrity sha512-F5bHvxSH+LvLV24vVB3L8K467dt3y3dio6V3W89dUz9nzvTpqd/HcT9zfYKL2aZPvD63vQFgLvaUX/UpUhrP6Q==
+jest-runtime@^29.5.0:
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.5.0.tgz#c83f943ee0c1da7eb91fa181b0811ebd59b03420"
+  integrity sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw==
   dependencies:
-    "@jest/environment" "^29.4.3"
-    "@jest/fake-timers" "^29.4.3"
-    "@jest/globals" "^29.4.3"
+    "@jest/environment" "^29.5.0"
+    "@jest/fake-timers" "^29.5.0"
+    "@jest/globals" "^29.5.0"
     "@jest/source-map" "^29.4.3"
-    "@jest/test-result" "^29.4.3"
-    "@jest/transform" "^29.4.3"
-    "@jest/types" "^29.4.3"
+    "@jest/test-result" "^29.5.0"
+    "@jest/transform" "^29.5.0"
+    "@jest/types" "^29.5.0"
     "@types/node" "*"
     chalk "^4.0.0"
     cjs-module-lexer "^1.0.0"
     collect-v8-coverage "^1.0.0"
     glob "^7.1.3"
     graceful-fs "^4.2.9"
-    jest-haste-map "^29.4.3"
-    jest-message-util "^29.4.3"
-    jest-mock "^29.4.3"
+    jest-haste-map "^29.5.0"
+    jest-message-util "^29.5.0"
+    jest-mock "^29.5.0"
     jest-regex-util "^29.4.3"
-    jest-resolve "^29.4.3"
-    jest-snapshot "^29.4.3"
-    jest-util "^29.4.3"
+    jest-resolve "^29.5.0"
+    jest-snapshot "^29.5.0"
+    jest-util "^29.5.0"
     slash "^3.0.0"
     strip-bom "^4.0.0"
 
@@ -8361,10 +9870,10 @@ jest-snapshot@^27.5.1:
     pretty-format "^27.5.1"
     semver "^7.3.2"
 
-jest-snapshot@^29.4.3:
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.4.3.tgz#183d309371450d9c4a3de7567ed2151eb0e91145"
-  integrity sha512-NGlsqL0jLPDW91dz304QTM/SNO99lpcSYYAjNiX0Ou+sSGgkanKBcSjCfp/pqmiiO1nQaOyLp6XQddAzRcx3Xw==
+jest-snapshot@^29.5.0:
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.5.0.tgz#c9c1ce0331e5b63cd444e2f95a55a73b84b1e8ce"
+  integrity sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g==
   dependencies:
     "@babel/core" "^7.11.6"
     "@babel/generator" "^7.7.2"
@@ -8372,23 +9881,22 @@ jest-snapshot@^29.4.3:
     "@babel/plugin-syntax-typescript" "^7.7.2"
     "@babel/traverse" "^7.7.2"
     "@babel/types" "^7.3.3"
-    "@jest/expect-utils" "^29.4.3"
-    "@jest/transform" "^29.4.3"
-    "@jest/types" "^29.4.3"
+    "@jest/expect-utils" "^29.5.0"
+    "@jest/transform" "^29.5.0"
+    "@jest/types" "^29.5.0"
     "@types/babel__traverse" "^7.0.6"
     "@types/prettier" "^2.1.5"
     babel-preset-current-node-syntax "^1.0.0"
     chalk "^4.0.0"
-    expect "^29.4.3"
+    expect "^29.5.0"
     graceful-fs "^4.2.9"
-    jest-diff "^29.4.3"
+    jest-diff "^29.5.0"
     jest-get-type "^29.4.3"
-    jest-haste-map "^29.4.3"
-    jest-matcher-utils "^29.4.3"
-    jest-message-util "^29.4.3"
-    jest-util "^29.4.3"
+    jest-matcher-utils "^29.5.0"
+    jest-message-util "^29.5.0"
+    jest-util "^29.5.0"
     natural-compare "^1.4.0"
-    pretty-format "^29.4.3"
+    pretty-format "^29.5.0"
     semver "^7.3.5"
 
 jest-util@^27.2.0, jest-util@^27.5.1:
@@ -8415,12 +9923,12 @@ jest-util@^28.1.3:
     graceful-fs "^4.2.9"
     picomatch "^2.2.3"
 
-jest-util@^29.4.3:
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.4.3.tgz#851a148e23fc2b633c55f6dad2e45d7f4579f496"
-  integrity sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==
+jest-util@^29.5.0:
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.5.0.tgz#24a4d3d92fc39ce90425311b23c27a6e0ef16b8f"
+  integrity sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==
   dependencies:
-    "@jest/types" "^29.4.3"
+    "@jest/types" "^29.5.0"
     "@types/node" "*"
     chalk "^4.0.0"
     ci-info "^3.2.0"
@@ -8451,17 +9959,39 @@ jest-validate@^27.5.1:
     leven "^3.1.0"
     pretty-format "^27.5.1"
 
-jest-validate@^29.4.3:
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.4.3.tgz#a13849dec4f9e95446a7080ad5758f58fa88642f"
-  integrity sha512-J3u5v7aPQoXPzaar6GndAVhdQcZr/3osWSgTeKg5v574I9ybX/dTyH0AJFb5XgXIB7faVhf+rS7t4p3lL9qFaw==
+jest-validate@^29.5.0:
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.5.0.tgz#8e5a8f36178d40e47138dc00866a5f3bd9916ffc"
+  integrity sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ==
   dependencies:
-    "@jest/types" "^29.4.3"
+    "@jest/types" "^29.5.0"
     camelcase "^6.2.0"
     chalk "^4.0.0"
     jest-get-type "^29.4.3"
     leven "^3.1.0"
-    pretty-format "^29.4.3"
+    pretty-format "^29.5.0"
+
+jest-watch-select-projects@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/jest-watch-select-projects/-/jest-watch-select-projects-2.0.0.tgz#4373d7e4de862aae28b46e036b669a4c913ea867"
+  integrity sha512-j00nW4dXc2NiCW6znXgFLF9g8PJ0zP25cpQ1xRro/HU2GBfZQFZD0SoXnAlaoKkIY4MlfTMkKGbNXFpvCdjl1w==
+  dependencies:
+    ansi-escapes "^4.3.0"
+    chalk "^3.0.0"
+    prompts "^2.2.1"
+
+jest-watch-typeahead@2.2.1:
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/jest-watch-typeahead/-/jest-watch-typeahead-2.2.1.tgz#36601520a2a30fd561788552dbda9c76bb44814a"
+  integrity sha512-jYpYmUnTzysmVnwq49TAxlmtOAwp8QIqvZyoofQFn8fiWhEDZj33ZXzg3JA4nGnzWFm1hbWf3ADpteUokvXgFA==
+  dependencies:
+    ansi-escapes "^6.0.0"
+    chalk "^4.0.0"
+    jest-regex-util "^29.0.0"
+    jest-watcher "^29.0.0"
+    slash "^5.0.0"
+    string-length "^5.0.1"
+    strip-ansi "^7.0.1"
 
 jest-watch-typeahead@^1.0.0:
   version "1.1.0"
@@ -8503,18 +10033,18 @@ jest-watcher@^28.0.0:
     jest-util "^28.1.3"
     string-length "^4.0.1"
 
-jest-watcher@^29.4.3:
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.4.3.tgz#e503baa774f0c2f8f3c8db98a22ebf885f19c384"
-  integrity sha512-zwlXH3DN3iksoIZNk73etl1HzKyi5FuQdYLnkQKm5BW4n8HpoG59xSwpVdFrnh60iRRaRBGw0gcymIxjJENPcA==
+jest-watcher@^29.0.0, jest-watcher@^29.5.0:
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.5.0.tgz#cf7f0f949828ba65ddbbb45c743a382a4d911363"
+  integrity sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA==
   dependencies:
-    "@jest/test-result" "^29.4.3"
-    "@jest/types" "^29.4.3"
+    "@jest/test-result" "^29.5.0"
+    "@jest/types" "^29.5.0"
     "@types/node" "*"
     ansi-escapes "^4.2.1"
     chalk "^4.0.0"
     emittery "^0.13.1"
-    jest-util "^29.4.3"
+    jest-util "^29.5.0"
     string-length "^4.0.1"
 
 jest-worker@^26.2.1:
@@ -8544,13 +10074,13 @@ jest-worker@^28.0.2:
     merge-stream "^2.0.0"
     supports-color "^8.0.0"
 
-jest-worker@^29.4.3:
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.4.3.tgz#9a4023e1ea1d306034237c7133d7da4240e8934e"
-  integrity sha512-GLHN/GTAAMEy5BFdvpUfzr9Dr80zQqBrh0fz1mtRMe05hqP45+HfQltu7oTBfduD0UeZs09d+maFtFYAXFWvAA==
+jest-worker@^29.5.0:
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.5.0.tgz#bdaefb06811bd3384d93f009755014d8acb4615d"
+  integrity sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==
   dependencies:
     "@types/node" "*"
-    jest-util "^29.4.3"
+    jest-util "^29.5.0"
     merge-stream "^2.0.0"
     supports-color "^8.0.0"
 
@@ -8564,14 +10094,19 @@ jest@^27.4.3:
     jest-cli "^27.5.1"
 
 jest@^29.4.3:
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/jest/-/jest-29.4.3.tgz#1b8be541666c6feb99990fd98adac4737e6e6386"
-  integrity sha512-XvK65feuEFGZT8OO0fB/QAQS+LGHvQpaadkH5p47/j3Ocqq3xf2pK9R+G0GzgfuhXVxEv76qCOOcMb5efLk6PA==
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/jest/-/jest-29.5.0.tgz#f75157622f5ce7ad53028f2f8888ab53e1f1f24e"
+  integrity sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==
   dependencies:
-    "@jest/core" "^29.4.3"
-    "@jest/types" "^29.4.3"
+    "@jest/core" "^29.5.0"
+    "@jest/types" "^29.5.0"
     import-local "^3.0.2"
-    jest-cli "^29.4.3"
+    jest-cli "^29.5.0"
+
+jimp-compact@0.16.1:
+  version "0.16.1"
+  resolved "https://registry.yarnpkg.com/jimp-compact/-/jimp-compact-0.16.1.tgz#9582aea06548a2c1e04dd148d7c3ab92075aefa3"
+  integrity sha512-dZ6Ra7u1G8c4Letq/B5EzAxj4tLFHL+cGtdpR+PVm4yzPDj+lCk+AbivWt1eOM+ikzkowtyV7qSqX6qr3t71Ww==
 
 joi@^17.2.1:
   version "17.8.3"
@@ -8584,6 +10119,11 @@ joi@^17.2.1:
     "@sideway/formula" "^3.0.1"
     "@sideway/pinpoint" "^2.0.0"
 
+join-component@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/join-component/-/join-component-1.1.0.tgz#b8417b750661a392bee2c2537c68b2a9d4977cd5"
+  integrity sha512-bF7vcQxbODoGK1imE2P9GS9aw4zD0Sd+Hni68IMZLj7zRnquH7dXUmMw9hDI5S/Jzt7q+IyTXN0rSg2GI0IKhQ==
+
 js-base64@^3.7.2:
   version "3.7.5"
   resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-3.7.5.tgz#21e24cf6b886f76d6f5f165bfcd69cc55b9e3fca"
@@ -8631,10 +10171,10 @@ js-yaml@^4.1.0:
   dependencies:
     argparse "^2.0.1"
 
-jsc-android@^250230.2.1:
-  version "250230.2.1"
-  resolved "https://registry.yarnpkg.com/jsc-android/-/jsc-android-250230.2.1.tgz#3790313a970586a03ab0ad47defbc84df54f1b83"
-  integrity sha512-KmxeBlRjwoqCnBBKGsihFtvsBHyUFlBxJPK4FzeYcIuBfdjv6jFys44JITAgSTbQD+vIdwMEfyZklsuQX0yI1Q==
+jsc-android@^250231.0.0:
+  version "250231.0.0"
+  resolved "https://registry.yarnpkg.com/jsc-android/-/jsc-android-250231.0.0.tgz#91720f8df382a108872fa4b3f558f33ba5e95262"
+  integrity sha512-rS46PvsjYmdmuz1OAWXY/1kCYG7pnf1TBqeTiOJr1iDz7s5DLxxC9n/ZMknLDxzYzNVfI7R95MH10emSSG1Wuw==
 
 jscodeshift@^0.13.1:
   version "0.13.1"
@@ -8694,6 +10234,38 @@ jsdom@^16.6.0:
     ws "^7.4.6"
     xml-name-validator "^3.0.0"
 
+jsdom@^20.0.0:
+  version "20.0.3"
+  resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-20.0.3.tgz#886a41ba1d4726f67a8858028c99489fed6ad4db"
+  integrity sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==
+  dependencies:
+    abab "^2.0.6"
+    acorn "^8.8.1"
+    acorn-globals "^7.0.0"
+    cssom "^0.5.0"
+    cssstyle "^2.3.0"
+    data-urls "^3.0.2"
+    decimal.js "^10.4.2"
+    domexception "^4.0.0"
+    escodegen "^2.0.0"
+    form-data "^4.0.0"
+    html-encoding-sniffer "^3.0.0"
+    http-proxy-agent "^5.0.0"
+    https-proxy-agent "^5.0.1"
+    is-potential-custom-element-name "^1.0.1"
+    nwsapi "^2.2.2"
+    parse5 "^7.1.1"
+    saxes "^6.0.0"
+    symbol-tree "^3.2.4"
+    tough-cookie "^4.1.2"
+    w3c-xmlserializer "^4.0.0"
+    webidl-conversions "^7.0.0"
+    whatwg-encoding "^2.0.0"
+    whatwg-mimetype "^3.0.0"
+    whatwg-url "^11.0.0"
+    ws "^8.11.0"
+    xml-name-validator "^4.0.0"
+
 jsesc@^2.5.1:
   version "2.5.2"
   resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
@@ -8719,6 +10291,20 @@ json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1:
   resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
   integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
 
+json-schema-deref-sync@^0.13.0:
+  version "0.13.0"
+  resolved "https://registry.yarnpkg.com/json-schema-deref-sync/-/json-schema-deref-sync-0.13.0.tgz#cb08b4ff435a48b5a149652d7750fdd071009823"
+  integrity sha512-YBOEogm5w9Op337yb6pAT6ZXDqlxAsQCanM3grid8lMWNxRJO/zWEJi3ZzqDL8boWfwhTFym5EFrNgWwpqcBRg==
+  dependencies:
+    clone "^2.1.2"
+    dag-map "~1.0.0"
+    is-valid-path "^0.1.1"
+    lodash "^4.17.13"
+    md5 "~2.2.0"
+    memory-cache "~0.2.0"
+    traverse "~0.6.6"
+    valid-url "~1.0.9"
+
 json-schema-traverse@^0.4.1:
   version "0.4.1"
   resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
@@ -8739,14 +10325,19 @@ json-stable-stringify-without-jsonify@^1.0.1:
   resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
   integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
 
-json5@^1.0.2:
+json5@^0.5.1:
+  version "0.5.1"
+  resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
+  integrity sha512-4xrs1aW+6N5DalkqSVA8fxh458CXvR99WU8WLKmq4v8eWAL86Xo3BVqyd3SkA9wEVjCMqyvvRRkshAdOnBp5rw==
+
+json5@^1.0.1, json5@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593"
   integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==
   dependencies:
     minimist "^1.2.0"
 
-json5@^2.1.1, json5@^2.1.2, json5@^2.2.0, json5@^2.2.2:
+json5@^2.1.0, json5@^2.1.1, json5@^2.1.2, json5@^2.2.0, json5@^2.2.2:
   version "2.2.3"
   resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
   integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
@@ -8886,15 +10477,27 @@ levn@~0.3.0:
     type-check "~0.3.2"
 
 lilconfig@^2.0.3, lilconfig@^2.0.5, lilconfig@^2.0.6:
-  version "2.0.6"
-  resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.6.tgz#32a384558bd58af3d4c6e077dd1ad1d397bc69d4"
-  integrity sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52"
+  integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==
 
 lines-and-columns@^1.1.6:
   version "1.2.4"
   resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
   integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
 
+linkify-it@^4.0.1:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-4.0.1.tgz#01f1d5e508190d06669982ba31a7d9f56a5751ec"
+  integrity sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==
+  dependencies:
+    uc.micro "^1.0.1"
+
+linkifyjs@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-4.1.0.tgz#0460bfcc37d3348fa80e078d92e7bbc82588db15"
+  integrity sha512-Ffv8VoY3+ixI1b3aZ3O+jM6x17cOsgwfB1Wq7pkytbo1WlyRp6ZO0YDMqiWT/gQPY/CmtiGuKfzDIVqxh1aCTA==
+
 loader-runner@^4.2.0:
   version "4.3.0"
   resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1"
@@ -9031,11 +10634,18 @@ lodash.uniq@^4.5.0:
   resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
   integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==
 
-lodash@^4.17.11, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0:
+lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.7.0:
   version "4.17.21"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
   integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
 
+log-symbols@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a"
+  integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==
+  dependencies:
+    chalk "^2.0.1"
+
 log-symbols@^4.1.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503"
@@ -9116,6 +10726,11 @@ make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0:
   dependencies:
     semver "^6.0.0"
 
+make-error@^1.3.6:
+  version "1.3.6"
+  resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
+  integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
+
 makeerror@1.0.12:
   version "1.0.12"
   resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a"
@@ -9135,6 +10750,47 @@ map-visit@^1.0.0:
   dependencies:
     object-visit "^1.0.0"
 
+markdown-it@^13.0.1:
+  version "13.0.1"
+  resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-13.0.1.tgz#c6ecc431cacf1a5da531423fc6a42807814af430"
+  integrity sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==
+  dependencies:
+    argparse "^2.0.1"
+    entities "~3.0.1"
+    linkify-it "^4.0.1"
+    mdurl "^1.0.1"
+    uc.micro "^1.0.5"
+
+md5-file@^3.2.3:
+  version "3.2.3"
+  resolved "https://registry.yarnpkg.com/md5-file/-/md5-file-3.2.3.tgz#f9bceb941eca2214a4c0727f5e700314e770f06f"
+  integrity sha512-3Tkp1piAHaworfcCgH0jKbTvj1jWWFgbvh2cXaNCgHwyTCBxxvD1Y04rmfpvdPm1P4oXMOpm6+2H7sr7v9v8Fw==
+  dependencies:
+    buffer-alloc "^1.1.0"
+
+md5@^2.2.1:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f"
+  integrity sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==
+  dependencies:
+    charenc "0.0.2"
+    crypt "0.0.2"
+    is-buffer "~1.1.6"
+
+md5@~2.2.0:
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9"
+  integrity sha512-PlGG4z5mBANDGCKsYQe0CaUYHdZYZt8ZPZLmEt+Urf0W4GlpTX4HescwHU+dc9+Z/G/vZKYZYFrwgm9VxK6QOQ==
+  dependencies:
+    charenc "~0.0.1"
+    crypt "~0.0.1"
+    is-buffer "~1.1.1"
+
+md5hex@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/md5hex/-/md5hex-1.0.0.tgz#ed74b477a2ee9369f75efee2f08d5915e52a42e8"
+  integrity sha512-c2YOUbp33+6thdCUi34xIyOU/a7bvGKj/3DB1iaPMTuPHf/Q2d5s4sn1FaCOO43XkXggnb08y5W2PU8UNYNLKQ==
+
 mdn-data@2.0.14:
   version "2.0.14"
   resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50"
@@ -9145,6 +10801,11 @@ mdn-data@2.0.4:
   resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b"
   integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==
 
+mdurl@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
+  integrity sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==
+
 media-typer@0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
@@ -9162,6 +10823,11 @@ memoize-one@^5.0.0:
   resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
   integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
 
+memory-cache@~0.2.0:
+  version "0.2.0"
+  resolved "https://registry.yarnpkg.com/memory-cache/-/memory-cache-0.2.0.tgz#7890b01d52c00c8ebc9d533e1f8eb17e3034871a"
+  integrity sha512-OcjA+jzjOYzKmKS6IQVALHLVz+rNTMPoJvCztFaZxwG14wtAW7VRZjwTQu06vKCYOxh4jVnik7ya0SXTB0W+xA==
+
 merge-descriptors@1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
@@ -9611,11 +11277,16 @@ mime@1.6.0:
   resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
   integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
 
-mime@^2.4.1:
+mime@^2.4.1, mime@^2.4.4:
   version "2.6.0"
   resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367"
   integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==
 
+mimic-fn@^1.0.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022"
+  integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==
+
 mimic-fn@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
@@ -9631,10 +11302,10 @@ min-indent@^1.0.0:
   resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
   integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
 
-mini-css-extract-plugin@^2.4.5:
-  version "2.7.2"
-  resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.2.tgz#e049d3ea7d3e4e773aad585c6cb329ce0c7b72d7"
-  integrity sha512-EdlUizq13o0Pd+uCp+WO/JpkLvHRVGt97RqfeGhXqAcorYo1ypJSpkV+WDT0vY/kmh/p7wRdJNJtuyK540PXDw==
+mini-css-extract-plugin@^2.4.5, mini-css-extract-plugin@^2.5.2:
+  version "2.7.3"
+  resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.3.tgz#794aa4d598bf178a66b2a35fe287c3df3eac394e"
+  integrity sha512-CD9cXeKeXLcnMw8FZdtfrRrLaM7gwCl4nKuKn2YkY2Bw5wdlB8zU2cCzw+w2zS9RFvbrufTBkMCJACNPwqQA0w==
   dependencies:
     schema-utils "^4.0.0"
 
@@ -9662,6 +11333,54 @@ minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6:
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
   integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
 
+minipass-collect@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617"
+  integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==
+  dependencies:
+    minipass "^3.0.0"
+
+minipass-flush@^1.0.5:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373"
+  integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==
+  dependencies:
+    minipass "^3.0.0"
+
+minipass-pipeline@^1.2.2:
+  version "1.2.4"
+  resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c"
+  integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==
+  dependencies:
+    minipass "^3.0.0"
+
+minipass@3.1.6:
+  version "3.1.6"
+  resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.6.tgz#3b8150aa688a711a1521af5e8779c1d3bb4f45ee"
+  integrity sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ==
+  dependencies:
+    yallist "^4.0.0"
+
+minipass@^3.0.0, minipass@^3.1.1:
+  version "3.3.6"
+  resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a"
+  integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==
+  dependencies:
+    yallist "^4.0.0"
+
+minipass@^4.0.0:
+  version "4.2.4"
+  resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.2.4.tgz#7d0d97434b6a19f59c5c3221698b48bbf3b2cd06"
+  integrity sha512-lwycX3cBMTvcejsHITUgYj6Gy6A7Nh4Q6h9NP4sTHY1ccJlC7yKzDmiShEHsJ16Jf1nKGDEaiHxiltsJEvk0nQ==
+
+minizlib@^2.1.1:
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
+  integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==
+  dependencies:
+    minipass "^3.0.0"
+    yallist "^4.0.0"
+
 mixin-deep@^1.2.0:
   version "1.3.2"
   resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566"
@@ -9682,15 +11401,15 @@ mkdirp@^0.5.1, mkdirp@~0.5.1:
   dependencies:
     minimist "^1.2.6"
 
-mkdirp@^1.0.4:
+mkdirp@^1.0.3, mkdirp@^1.0.4:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
   integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
 
 mobx-react-lite@^3.4.0:
-  version "3.4.0"
-  resolved "https://registry.yarnpkg.com/mobx-react-lite/-/mobx-react-lite-3.4.0.tgz#d59156a96889cdadad751e5e4dab95f28926dfff"
-  integrity sha512-bRuZp3C0itgLKHu/VNxi66DN/XVkQG7xtoBVWxpvC5FhAqbOCP21+nPhULjnzEqd7xBMybp6KwytdUpZKEgpIQ==
+  version "3.4.3"
+  resolved "https://registry.yarnpkg.com/mobx-react-lite/-/mobx-react-lite-3.4.3.tgz#3a4c22c30bfaa8b1b2aa48d12b2ba811c0947ab7"
+  integrity sha512-NkJREyFTSUXR772Qaai51BnE1voWx56LOL80xG7qkZr6vo8vEaLF3sz1JNUVh+rxmUzxYaqOhfuxTfqUh0FXUg==
 
 mobx@^6.6.1:
   version "6.8.0"
@@ -9752,12 +11471,21 @@ mv@~2:
     ncp "~2.0.0"
     rimraf "~2.4.0"
 
+mz@^2.7.0:
+  version "2.7.0"
+  resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32"
+  integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==
+  dependencies:
+    any-promise "^1.0.0"
+    object-assign "^4.0.1"
+    thenify-all "^1.0.0"
+
 nan@^2.14.0:
   version "2.17.0"
   resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb"
   integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==
 
-nanoid@^3.3.1, nanoid@^3.3.4:
+nanoid@^3.1.23, nanoid@^3.3.1, nanoid@^3.3.4:
   version "3.3.4"
   resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
   integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
@@ -9809,6 +11537,11 @@ neo-async@^2.5.0, neo-async@^2.6.0, neo-async@^2.6.2:
   resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
   integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
 
+nested-error-stacks@~2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/nested-error-stacks/-/nested-error-stacks-2.0.1.tgz#d2cc9fc5235ddb371fc44d506234339c8e4b0a4b"
+  integrity sha512-SrQrok4CATudVzBS7coSz26QRSmlK9TzzoFbeKfcPBUFPjcQM9Rqvr/DlJkOrwI/0KcgvMub1n1g5Jt9EgRn4A==
+
 nice-try@^1.0.4:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
@@ -9853,18 +11586,26 @@ node-fetch@2.6.7:
   dependencies:
     whatwg-url "^5.0.0"
 
-node-fetch@^2.2.0, node-fetch@^2.6.0:
+node-fetch@^2.2.0, node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.7:
   version "2.6.9"
   resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.9.tgz#7c7f744b5cc6eb5fd404e0c7a9fec630a55657e6"
   integrity sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==
   dependencies:
     whatwg-url "^5.0.0"
 
-node-forge@^1:
+node-forge@^1, node-forge@^1.2.1, node-forge@^1.3.1:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3"
   integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==
 
+node-html-parser@^5.2.0:
+  version "5.4.2"
+  resolved "https://registry.yarnpkg.com/node-html-parser/-/node-html-parser-5.4.2.tgz#93e004038c17af80226c942336990a0eaed8136a"
+  integrity sha512-RaBPP3+51hPne/OolXxcz89iYvQvKOydaqoePpOgXcrOKZhjVIzmpKZz+Hd/RBO2/zN2q6CNJhQzucVz+u3Jyw==
+  dependencies:
+    css-select "^4.2.1"
+    he "1.2.0"
+
 node-int64@^0.4.0:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
@@ -9911,16 +11652,6 @@ normalize-css-color@^1.0.2:
   resolved "https://registry.yarnpkg.com/normalize-css-color/-/normalize-css-color-1.0.2.tgz#02991e97cccec6623fe573afbbf0de6a1f3e9f8d"
   integrity sha512-jPJ/V7Cp1UytdidsPqviKEElFQJs22hUUgK5BOPHTwOonNCk7/2qOxhhqzEajmFrWJowADFfOFh1V+aWkRfy+w==
 
-normalize-package-data@^2.5.0:
-  version "2.5.0"
-  resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
-  integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==
-  dependencies:
-    hosted-git-info "^2.1.4"
-    resolve "^1.10.0"
-    semver "2 || 3 || 4 || 5"
-    validate-npm-package-license "^3.0.1"
-
 normalize-path@^3.0.0, normalize-path@~3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
@@ -9941,6 +11672,16 @@ normalize-url@^8.0.0:
   resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-8.0.0.tgz#593dbd284f743e8dcf6a5ddf8fadff149c82701a"
   integrity sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==
 
+npm-package-arg@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-7.0.0.tgz#52cdf08b491c0c59df687c4c925a89102ef794a5"
+  integrity sha512-xXxr8y5U0kl8dVkz2oK7yZjPBvqM2fwaO5l3Yg13p03v8+E3qQcD0JNhHzjL1vyGgxcKkD0cco+NLR72iuPk3g==
+  dependencies:
+    hosted-git-info "^3.0.2"
+    osenv "^0.1.5"
+    semver "^5.6.0"
+    validate-npm-package-name "^3.0.0"
+
 npm-run-path@^2.0.0:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
@@ -9974,7 +11715,7 @@ nullthrows@^1.1.1:
   resolved "https://registry.yarnpkg.com/nullthrows/-/nullthrows-1.1.1.tgz#7818258843856ae971eae4208ad7d7eb19a431b1"
   integrity sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==
 
-nwsapi@^2.2.0:
+nwsapi@^2.2.0, nwsapi@^2.2.2:
   version "2.2.2"
   resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.2.tgz#e5418863e7905df67d51ec95938d67bf801f0bb0"
   integrity sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==
@@ -9989,7 +11730,7 @@ ob1@0.73.8:
   resolved "https://registry.yarnpkg.com/ob1/-/ob1-0.73.8.tgz#c569f1a15ce2d04da6fd70293ad44b5a93b11978"
   integrity sha512-1F7j+jzD+edS6ohQP7Vg5f3yiIk5i3x1uLrNIHOmLHWzWK1t3zrDpjnoXghccdVlsU+UjbyURnDynm4p0GgXeA==
 
-object-assign@^4, object-assign@^4.1.0, object-assign@^4.1.1:
+object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
   integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
@@ -10079,6 +11820,13 @@ object.hasown@^1.1.2:
     define-properties "^1.1.4"
     es-abstract "^1.20.4"
 
+object.omit@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-3.0.0.tgz#0e3edc2fce2ba54df5577ff529f6d97bd8a522af"
+  integrity sha512-EO+BCv6LJfu+gBIF3ggLicFebFLN5zqzz/WWJlMFfkMyGth+oBkhxzDl0wx2W4GkLzuQs/FsSkXZb2IMWQqmBQ==
+  dependencies:
+    is-extendable "^1.0.0"
+
 object.pick@^1.3.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747"
@@ -10136,6 +11884,13 @@ one-webcrypto@^1.0.3:
   resolved "https://registry.yarnpkg.com/one-webcrypto/-/one-webcrypto-1.0.3.tgz#f951243cde29b79b6745ad14966fc598a609997c"
   integrity sha512-fu9ywBVBPx0gS9K0etIROTiCkvI5S1TDjFsYFb3rC1ewFxeOqsbzq7aIMBHsYfrTHBcGXJaONXXjTl8B01cW1Q==
 
+onetime@^2.0.0:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4"
+  integrity sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==
+  dependencies:
+    mimic-fn "^1.0.0"
+
 onetime@^5.1.0, onetime@^5.1.2:
   version "5.1.2"
   resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e"
@@ -10150,7 +11905,7 @@ open@^6.2.0:
   dependencies:
     is-wsl "^1.1.0"
 
-open@^8.0.9, open@^8.4.0:
+open@^8.0.4, open@^8.0.9, open@^8.3.0, open@^8.4.0:
   version "8.4.2"
   resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9"
   integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==
@@ -10188,6 +11943,18 @@ optionator@^0.9.1:
     type-check "^0.4.0"
     word-wrap "^1.2.3"
 
+ora@3.4.0:
+  version "3.4.0"
+  resolved "https://registry.yarnpkg.com/ora/-/ora-3.4.0.tgz#bf0752491059a3ef3ed4c85097531de9fdbcd318"
+  integrity sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg==
+  dependencies:
+    chalk "^2.4.2"
+    cli-cursor "^2.1.0"
+    cli-spinners "^2.0.0"
+    log-symbols "^2.2.0"
+    strip-ansi "^5.2.0"
+    wcwidth "^1.0.1"
+
 ora@^5.4.1:
   version "5.4.1"
   resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18"
@@ -10203,11 +11970,29 @@ ora@^5.4.1:
     strip-ansi "^6.0.0"
     wcwidth "^1.0.1"
 
-os-tmpdir@^1.0.0:
+orderedmap@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/orderedmap/-/orderedmap-2.1.0.tgz#819457082fa3a06abd316d83a281a1ca467437cd"
+  integrity sha512-/pIFexOm6S70EPdznemIz3BQZoJ4VTFrhqzu0ACBqBgeLsLxq8e6Jim63ImIfwW/zAD1AlXpRMlOv3aghmo4dA==
+
+os-homedir@^1.0.0:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
+  integrity sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==
+
+os-tmpdir@^1.0.0, os-tmpdir@~1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
   integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==
 
+osenv@^0.1.5:
+  version "0.1.5"
+  resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410"
+  integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==
+  dependencies:
+    os-homedir "^1.0.0"
+    os-tmpdir "^1.0.0"
+
 p-finally@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
@@ -10248,6 +12033,18 @@ p-locate@^5.0.0:
   dependencies:
     p-limit "^3.0.2"
 
+p-map@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175"
+  integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==
+
+p-map@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b"
+  integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==
+  dependencies:
+    aggregate-error "^3.0.0"
+
 p-queue@^6.6.2:
   version "6.6.2"
   resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426"
@@ -10321,11 +12118,25 @@ parse-json@^5.0.0, parse-json@^5.2.0:
     json-parse-even-better-errors "^2.3.0"
     lines-and-columns "^1.1.6"
 
+parse-png@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/parse-png/-/parse-png-2.1.0.tgz#2a42ad719fedf90f81c59ebee7ae59b280d6b338"
+  integrity sha512-Nt/a5SfCLiTnQAjx3fHlqp8hRgTL3z7kTQZzvIMS9uCAepnCyjpdEc6M/sz69WqMBdaDBw9sF1F1UaHROYzGkQ==
+  dependencies:
+    pngjs "^3.3.0"
+
 parse5@6.0.1:
   version "6.0.1"
   resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
   integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==
 
+parse5@^7.0.0, parse5@^7.1.1:
+  version "7.1.2"
+  resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32"
+  integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==
+  dependencies:
+    entities "^4.4.0"
+
 parseurl@~1.3.2, parseurl@~1.3.3:
   version "1.3.3"
   resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
@@ -10344,6 +12155,19 @@ pascalcase@^0.1.1:
   resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14"
   integrity sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==
 
+password-prompt@^1.0.4:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/password-prompt/-/password-prompt-1.1.2.tgz#85b2f93896c5bd9e9f2d6ff0627fa5af3dc00923"
+  integrity sha512-bpuBhROdrhuN3E7G/koAju0WjVw9/uQOG5Co5mokNj0MiOSBVZS1JTwM4zl55hu0WFmIEFvO9cU9sJQiBIYeIA==
+  dependencies:
+    ansi-escapes "^3.1.0"
+    cross-spawn "^6.0.5"
+
+path-browserify@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd"
+  integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==
+
 path-exists@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
@@ -10359,6 +12183,11 @@ path-is-absolute@^1.0.0:
   resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
   integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==
 
+path-is-inside@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
+  integrity sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==
+
 path-key@^2.0.0, path-key@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
@@ -10369,7 +12198,7 @@ path-key@^3.0.0, path-key@^3.1.0:
   resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
   integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
 
-path-parse@^1.0.7:
+path-parse@^1.0.5, path-parse@^1.0.7:
   version "1.0.7"
   resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
   integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
@@ -10404,10 +12233,10 @@ pg-int8@1.0.1:
   resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c"
   integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==
 
-pg-pool@^3.5.2:
-  version "3.5.2"
-  resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.5.2.tgz#ed1bed1fb8d79f1c6fd5fb1c99e990fbf9ddf178"
-  integrity sha512-His3Fh17Z4eg7oANLob6ZvH8xIVen3phEZh2QuyrIl4dQSDVEabNducv6ysROKpDNPSD+12tONZVWfSgMvDD9w==
+pg-pool@^3.6.0:
+  version "3.6.0"
+  resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.6.0.tgz#3190df3e4747a0d23e5e9e8045bcd99bda0a712e"
+  integrity sha512-clFRf2ksqd+F497kWFyM21tMjeikn60oGDmqMT8UBrynEwVEX/5R5xd2sdvdo1cZCFlguORNpVuqxIj+aK4cfQ==
 
 pg-protocol@^1.6.0:
   version "1.6.0"
@@ -10426,14 +12255,14 @@ pg-types@^2.1.0:
     postgres-interval "^1.1.0"
 
 pg@^8.8.0:
-  version "8.9.0"
-  resolved "https://registry.yarnpkg.com/pg/-/pg-8.9.0.tgz#73c5d77a854d36b0e185450dacb8b90c669e040b"
-  integrity sha512-ZJM+qkEbtOHRuXjmvBtOgNOXOtLSbxiMiUVMgE4rV6Zwocy03RicCVvDXgx8l4Biwo8/qORUnEqn2fdQzV7KCg==
+  version "8.10.0"
+  resolved "https://registry.yarnpkg.com/pg/-/pg-8.10.0.tgz#5b8379c9b4a36451d110fc8cd98fc325fe62ad24"
+  integrity sha512-ke7o7qSTMb47iwzOSaZMfeR7xToFdkE71ifIipOAAaLIM0DYzfOAXlgFFmYUIE2BcJtvnVlGCID84ZzCegE8CQ==
   dependencies:
     buffer-writer "2.0.0"
     packet-reader "1.0.0"
     pg-connection-string "^2.5.0"
-    pg-pool "^3.5.2"
+    pg-pool "^3.6.0"
     pg-protocol "^1.6.0"
     pg-types "^2.1.0"
     pgpass "1.x"
@@ -10460,7 +12289,7 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.2.3, picomatc
   resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
   integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
 
-pify@^2.3.0:
+pify@^2.0.0, pify@^2.3.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
   integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==
@@ -10470,6 +12299,18 @@ pify@^4.0.1:
   resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231"
   integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==
 
+pinkie-promise@^2.0.0:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
+  integrity sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==
+  dependencies:
+    pinkie "^2.0.0"
+
+pinkie@^2.0.0:
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
+  integrity sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==
+
 pino-abstract-transport@v1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-1.0.0.tgz#cc0d6955fffcadb91b7b49ef220a6cc111d48bb3"
@@ -10510,7 +12351,7 @@ pino@^8.0.0, pino@^8.6.1:
     sonic-boom "^3.1.0"
     thread-stream "^2.0.0"
 
-pirates@^4.0.4, pirates@^4.0.5:
+pirates@^4.0.1, pirates@^4.0.4, pirates@^4.0.5:
   version "4.0.5"
   resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b"
   integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==
@@ -10536,6 +12377,24 @@ pkg-up@^3.1.0:
   dependencies:
     find-up "^3.0.0"
 
+plist@^3.0.5:
+  version "3.0.6"
+  resolved "https://registry.yarnpkg.com/plist/-/plist-3.0.6.tgz#7cfb68a856a7834bca6dbfe3218eb9c7740145d3"
+  integrity sha512-WiIVYyrp8TD4w8yCvyeIr+lkmrGRd5u0VbRnU+tP/aRLxP/YadJUYOMZJ/6hIa3oUyVCsycXvtNRgd5XBJIbiA==
+  dependencies:
+    base64-js "^1.5.1"
+    xmlbuilder "^15.1.1"
+
+pngjs@^3.3.0:
+  version "3.4.0"
+  resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f"
+  integrity sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==
+
+pngjs@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb"
+  integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==
+
 posix-character-classes@^0.1.0:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
@@ -11155,7 +13014,7 @@ prettier@^2.8.3:
   resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.4.tgz#34dd2595629bfbb79d344ac4a91ff948694463c3"
   integrity sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==
 
-pretty-bytes@^5.3.0, pretty-bytes@^5.4.1:
+pretty-bytes@5.6.0, pretty-bytes@^5.3.0, pretty-bytes@^5.4.1:
   version "5.6.0"
   resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"
   integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==
@@ -11168,7 +13027,7 @@ pretty-error@^4.0.0:
     lodash "^4.17.20"
     renderkid "^3.0.0"
 
-pretty-format@^26.0.0, pretty-format@^26.5.2, pretty-format@^26.6.2:
+pretty-format@^26.5.2, pretty-format@^26.6.2:
   version "26.6.2"
   resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93"
   integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==
@@ -11197,10 +13056,10 @@ pretty-format@^28.1.3:
     ansi-styles "^5.0.0"
     react-is "^18.0.0"
 
-pretty-format@^29.0.3, pretty-format@^29.4.0, pretty-format@^29.4.3:
-  version "29.4.3"
-  resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.4.3.tgz#25500ada21a53c9e8423205cf0337056b201244c"
-  integrity sha512-cvpcHTc42lcsvOOAzd3XuNWTcvk1Jmnzqeu+WsOuiPmxUJTnkbAcFNsRKvEpBEUFVUgy/GTZLulZDcDEi+CIlA==
+pretty-format@^29.0.0, pretty-format@^29.0.3, pretty-format@^29.5.0:
+  version "29.5.0"
+  resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.5.0.tgz#283134e74f70e2e3e7229336de0e4fce94ccde5a"
+  integrity sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==
   dependencies:
     "@jest/schemas" "^29.4.3"
     ansi-styles "^5.0.0"
@@ -11221,6 +13080,16 @@ process@^0.11.10:
   resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
   integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==
 
+progress@2.0.3:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
+  integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
+
+promise-inflight@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
+  integrity sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==
+
 promise-polyfill@^6.0.1:
   version "6.1.0"
   resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-6.1.0.tgz#dfa96943ea9c121fca4de9b5868cb39d3472e057"
@@ -11240,7 +13109,7 @@ promise@^8.1.0, promise@^8.3.0:
   dependencies:
     asap "~2.0.6"
 
-prompts@^2.0.1, prompts@^2.4.0, prompts@^2.4.2:
+prompts@^2.0.1, prompts@^2.2.1, prompts@^2.3.2, prompts@^2.4.0, prompts@^2.4.2:
   version "2.4.2"
   resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069"
   integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==
@@ -11248,7 +13117,7 @@ prompts@^2.0.1, prompts@^2.4.0, prompts@^2.4.2:
     kleur "^3.0.3"
     sisteransi "^1.0.5"
 
-prop-types@*, prop-types@^15.5.10, prop-types@^15.7.2, prop-types@^15.8.1:
+prop-types@*, prop-types@^15.7.2, prop-types@^15.8.1:
   version "15.8.1"
   resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
   integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@@ -11266,6 +13135,160 @@ proper-lockfile@^3.0.2:
     retry "^0.12.0"
     signal-exit "^3.0.2"
 
+prosemirror-changeset@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/prosemirror-changeset/-/prosemirror-changeset-2.2.0.tgz#22c05da271a118be40d3e339fa2cace789b1254b"
+  integrity sha512-QM7ohGtkpVpwVGmFb8wqVhaz9+6IUXcIQBGZ81YNAKYuHiFJ1ShvSzab4pKqTinJhwciZbrtBEk/2WsqSt2PYg==
+  dependencies:
+    prosemirror-transform "^1.0.0"
+
+prosemirror-collab@^1.3.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/prosemirror-collab/-/prosemirror-collab-1.3.0.tgz#601d33473bf72e6c43041a54b860c84c60b37769"
+  integrity sha512-+S/IJ69G2cUu2IM5b3PBekuxs94HO1CxJIWOFrLQXUaUDKL/JfBx+QcH31ldBlBXyDEUl+k3Vltfi1E1MKp2mA==
+  dependencies:
+    prosemirror-state "^1.0.0"
+
+prosemirror-commands@^1.0.0, prosemirror-commands@^1.3.1:
+  version "1.5.1"
+  resolved "https://registry.yarnpkg.com/prosemirror-commands/-/prosemirror-commands-1.5.1.tgz#89ddfa14e144dcc7fb0938aa0e2568c7fdde306f"
+  integrity sha512-ga1ga/RkbzxfAvb6iEXYmrEpekn5NCwTb8w1dr/gmhSoaGcQ0VPuCzOn5qDEpC45ql2oDkKoKQbRxLJwKLpMTQ==
+  dependencies:
+    prosemirror-model "^1.0.0"
+    prosemirror-state "^1.0.0"
+    prosemirror-transform "^1.0.0"
+
+prosemirror-dropcursor@^1.5.0:
+  version "1.7.1"
+  resolved "https://registry.yarnpkg.com/prosemirror-dropcursor/-/prosemirror-dropcursor-1.7.1.tgz#b6921ef866ca95b6f6c8b197767f60dc39598416"
+  integrity sha512-GmWk9bAwhfHwA8xmJhBFjPcebxUG9zAPYtqpIr7NTDigWZZEJCgUYyUQeqgyscLr8ZHoh9aeprX9kW7BihUT+w==
+  dependencies:
+    prosemirror-state "^1.0.0"
+    prosemirror-transform "^1.1.0"
+    prosemirror-view "^1.1.0"
+
+prosemirror-gapcursor@^1.3.1:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/prosemirror-gapcursor/-/prosemirror-gapcursor-1.3.1.tgz#8cfd874592e4504d63720e14ed680c7866e64554"
+  integrity sha512-GKTeE7ZoMsx5uVfc51/ouwMFPq0o8YrZ7Hx4jTF4EeGbXxBveUV8CGv46mSHuBBeXGmvu50guoV2kSnOeZZnUA==
+  dependencies:
+    prosemirror-keymap "^1.0.0"
+    prosemirror-model "^1.0.0"
+    prosemirror-state "^1.0.0"
+    prosemirror-view "^1.0.0"
+
+prosemirror-history@^1.0.0, prosemirror-history@^1.3.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/prosemirror-history/-/prosemirror-history-1.3.0.tgz#bf5a1ff7759aca759ddf0c722c2fa5b14fb0ddc1"
+  integrity sha512-qo/9Wn4B/Bq89/YD+eNWFbAytu6dmIM85EhID+fz9Jcl9+DfGEo8TTSrRhP15+fFEoaPqpHSxlvSzSEbmlxlUA==
+  dependencies:
+    prosemirror-state "^1.2.2"
+    prosemirror-transform "^1.0.0"
+    rope-sequence "^1.3.0"
+
+prosemirror-inputrules@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/prosemirror-inputrules/-/prosemirror-inputrules-1.2.0.tgz#476dde2dc244050b3aca00cf58a82adfad6749e7"
+  integrity sha512-eAW/M/NTSSzpCOxfR8Abw6OagdG0MiDAiWHQMQveIsZtoKVYzm0AflSPq/ymqJd56/Su1YPbwy9lM13wgHOFmQ==
+  dependencies:
+    prosemirror-state "^1.0.0"
+    prosemirror-transform "^1.0.0"
+
+prosemirror-keymap@^1.0.0, prosemirror-keymap@^1.1.2, prosemirror-keymap@^1.2.0:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/prosemirror-keymap/-/prosemirror-keymap-1.2.1.tgz#3839e7db66cecddae7451f4246e73bdd8489be1d"
+  integrity sha512-kVK6WGC+83LZwuSJnuCb9PsADQnFZllt94qPP3Rx/vLcOUV65+IbBeH2nS5cFggPyEVJhGkGrgYFRrG250WhHQ==
+  dependencies:
+    prosemirror-state "^1.0.0"
+    w3c-keyname "^2.2.0"
+
+prosemirror-markdown@^1.10.1:
+  version "1.10.1"
+  resolved "https://registry.yarnpkg.com/prosemirror-markdown/-/prosemirror-markdown-1.10.1.tgz#e20468201cda1916a6182686159398b242bb78ab"
+  integrity sha512-s7iaTLiX+qO5z8kF2NcMmy2T7mIlxzkS4Sp3vTKSYChPtbMpg6YxFkU0Y06rUg2WtKlvBu7v1bXzlGBkfjUWAA==
+  dependencies:
+    markdown-it "^13.0.1"
+    prosemirror-model "^1.0.0"
+
+prosemirror-menu@^1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/prosemirror-menu/-/prosemirror-menu-1.2.1.tgz#94d99a8547b7ba5680c20e9c497ce19846ce3b2c"
+  integrity sha512-sBirXxVfHalZO4f1ZS63WzewINK4182+7dOmoMeBkqYO8wqMBvBS7wQuwVOHnkMWPEh0+N0LJ856KYUN+vFkmQ==
+  dependencies:
+    crelt "^1.0.0"
+    prosemirror-commands "^1.0.0"
+    prosemirror-history "^1.0.0"
+    prosemirror-state "^1.0.0"
+
+prosemirror-model@^1.0.0, prosemirror-model@^1.16.0, prosemirror-model@^1.18.1, prosemirror-model@^1.19.0, prosemirror-model@^1.8.1:
+  version "1.19.0"
+  resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.19.0.tgz#d7ad9a65ada0bb12196f64fe0dd4fc392c841c29"
+  integrity sha512-/CvFGJnwc41EJSfDkQLly1cAJJJmBpZwwUJtwZPTjY2RqZJfM8HVbCreOY/jti8wTRbVyjagcylyGoeJH/g/3w==
+  dependencies:
+    orderedmap "^2.0.0"
+
+prosemirror-schema-basic@^1.2.0:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.1.tgz#a5a137a6399d1a829873332117d2fe8131d291d0"
+  integrity sha512-vYBdIHsYKSDIqYmPBC7lnwk9DsKn8PnVqK97pMYP5MLEDFqWIX75JiaJTzndBii4bRuNqhC2UfDOfM3FKhlBHg==
+  dependencies:
+    prosemirror-model "^1.19.0"
+
+prosemirror-schema-list@^1.2.2:
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/prosemirror-schema-list/-/prosemirror-schema-list-1.2.2.tgz#bafda37b72367d39accdcaf6ddf8fb654a16e8e5"
+  integrity sha512-rd0pqSDp86p0MUMKG903g3I9VmElFkQpkZ2iOd3EOVg1vo5Cst51rAsoE+5IPy0LPXq64eGcCYlW1+JPNxOj2w==
+  dependencies:
+    prosemirror-model "^1.0.0"
+    prosemirror-state "^1.0.0"
+    prosemirror-transform "^1.0.0"
+
+prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, prosemirror-state@^1.3.1, prosemirror-state@^1.4.1:
+  version "1.4.2"
+  resolved "https://registry.yarnpkg.com/prosemirror-state/-/prosemirror-state-1.4.2.tgz#f93bd8a33a4454efab917ba9b738259d828db7e5"
+  integrity sha512-puuzLD2mz/oTdfgd8msFbe0A42j5eNudKAAPDB0+QJRw8cO1ygjLmhLrg9RvDpf87Dkd6D4t93qdef00KKNacQ==
+  dependencies:
+    prosemirror-model "^1.0.0"
+    prosemirror-transform "^1.0.0"
+    prosemirror-view "^1.27.0"
+
+prosemirror-tables@^1.3.0:
+  version "1.3.2"
+  resolved "https://registry.yarnpkg.com/prosemirror-tables/-/prosemirror-tables-1.3.2.tgz#ca208c6a55d510af14b652d23e800e00ba6bebd4"
+  integrity sha512-/9JTeN6s58Zq66HXaxP6uf8PAmc7XXKZFPlOGVtLvxEd6xBP6WtzaJB9wBjiGUzwbdhdMEy7V62yuHqk/3VrnQ==
+  dependencies:
+    prosemirror-keymap "^1.1.2"
+    prosemirror-model "^1.8.1"
+    prosemirror-state "^1.3.1"
+    prosemirror-transform "^1.2.1"
+    prosemirror-view "^1.13.3"
+
+prosemirror-trailing-node@^2.0.2:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/prosemirror-trailing-node/-/prosemirror-trailing-node-2.0.3.tgz#213fc0e545a434ff3c37b5218a0de69561bf3892"
+  integrity sha512-lGrjMrn97KWkjQSW/FjdvnhJmqFACmQIyr6lKYApvHitDnKsCoZz6XzrHB7RZYHni/0NxQmZ01p/2vyK2SkvaA==
+  dependencies:
+    "@babel/runtime" "^7.13.10"
+    "@remirror/core-constants" "^2.0.0"
+    "@remirror/core-helpers" "^2.0.1"
+    escape-string-regexp "^4.0.0"
+
+prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transform@^1.2.1, prosemirror-transform@^1.7.0:
+  version "1.7.1"
+  resolved "https://registry.yarnpkg.com/prosemirror-transform/-/prosemirror-transform-1.7.1.tgz#b516e818c3add0bdf960f4ca8ccb9d057a3ba21b"
+  integrity sha512-VteoifAfpt46z0yEt6Fc73A5OID9t/y2QIeR5MgxEwTuitadEunD/V0c9jQW8ziT8pbFM54uTzRLJ/nLuQjMxg==
+  dependencies:
+    prosemirror-model "^1.0.0"
+
+prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.13.3, prosemirror-view@^1.27.0, prosemirror-view@^1.28.2:
+  version "1.30.1"
+  resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.30.1.tgz#7cf0ae8dc8553a02c32961e82eca25079c4d8fc9"
+  integrity sha512-pZUfr7lICJkEY7XwzldAKrkflZDeIvnbfuu2RIS01N5NwJmR/dfZzDzJRzhb3SM2QtT/bM8b4Nnib8X3MGpAhA==
+  dependencies:
+    prosemirror-model "^1.16.0"
+    prosemirror-state "^1.0.0"
+    prosemirror-transform "^1.1.0"
+
 proxy-addr@~2.0.7:
   version "2.0.7"
   resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025"
@@ -11297,18 +13320,45 @@ punycode@^2.1.0, punycode@^2.1.1:
   resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f"
   integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==
 
+pure-rand@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.0.tgz#701996ceefa253507923a0e864c17ab421c04a7c"
+  integrity sha512-rLSBxJjP+4DQOgcJAx6RZHT2he2pkhQdSnofG5VWyVl6GRq/K02ISOuOLcsMOrtKDIJb8JN2zm3FFzWNbezdPw==
+
 q@^1.1.2:
   version "1.5.1"
   resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
   integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==
 
-qs@6.11.0, qs@^6.5.1:
+qrcode-terminal@0.11.0:
+  version "0.11.0"
+  resolved "https://registry.yarnpkg.com/qrcode-terminal/-/qrcode-terminal-0.11.0.tgz#ffc6c28a2fc0bfb47052b47e23f4f446a5fbdb9e"
+  integrity sha512-Uu7ii+FQy4Qf82G4xu7ShHhjhGahEpCWc3x8UavY3CTcWV+ufmmCtwkr7ZKsX42jdL0kr1B5FKUeqJvAn51jzQ==
+
+qs@6.11.0:
   version "6.11.0"
   resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a"
   integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==
   dependencies:
     side-channel "^1.0.4"
 
+qs@^6.5.1:
+  version "6.11.1"
+  resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.1.tgz#6c29dff97f0c0060765911ba65cbc9764186109f"
+  integrity sha512-0wsrzgTz/kAVIeuxSjnpGC56rzYtr6JT/2BwEvMaPhFIoYa1aGO8LbzuU1R0uUYQkLpWBTOj0l/CLAJB64J6nQ==
+  dependencies:
+    side-channel "^1.0.4"
+
+query-string@^7.1.3:
+  version "7.1.3"
+  resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.1.3.tgz#a1cf90e994abb113a325804a972d98276fe02328"
+  integrity sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==
+  dependencies:
+    decode-uri-component "^0.2.2"
+    filter-obj "^1.1.0"
+    split-on-first "^1.0.0"
+    strict-uri-encode "^2.0.0"
+
 querystringify@^2.1.1:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
@@ -11358,7 +13408,17 @@ raw-body@2.5.1:
     iconv-lite "0.4.24"
     unpipe "1.0.0"
 
-rc@^1.2.7:
+raw-body@2.5.2:
+  version "2.5.2"
+  resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a"
+  integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==
+  dependencies:
+    bytes "3.1.2"
+    http-errors "2.0.0"
+    iconv-lite "0.4.24"
+    unpipe "1.0.0"
+
+rc@^1.0.1, rc@^1.1.6, rc@^1.2.7, rc@~1.2.7:
   version "1.2.8"
   resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
   integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
@@ -11455,7 +13515,7 @@ react-freeze@^1.0.0:
   resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
   integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
 
-react-is@^16.13.1, react-is@^16.7.0:
+react-is@^16.13.0, react-is@^16.13.1, react-is@^16.7.0:
   version "16.13.1"
   resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
   integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@@ -11475,7 +13535,7 @@ react-native-background-fetch@^4.1.8:
   resolved "https://registry.yarnpkg.com/react-native-background-fetch/-/react-native-background-fetch-4.1.8.tgz#a21858e5d876de8d9d15a37f40714b244f73713c"
   integrity sha512-/qe86laa0n4AbD6mrLL8SCGR+K5693URX95e02/bTJh3UkdS3+sU1Jyc/XTlz4MQwlquI929/lm5EZh8AOUqzQ==
 
-react-native-codegen@^0.71.3:
+react-native-codegen@^0.71.5:
   version "0.71.5"
   resolved "https://registry.yarnpkg.com/react-native-codegen/-/react-native-codegen-0.71.5.tgz#454a42a891cd4ca5fc436440d301044dc1349c14"
   integrity sha512-rfsuc0zkuUuMjFnrT55I1mDZ+pBRp2zAiRwxck3m6qeGJBGK5OV5JH66eDQ4aa+3m0of316CqrJDRzVlYufzIg==
@@ -11492,6 +13552,13 @@ react-native-dotenv@^3.3.1:
   dependencies:
     dotenv "^16.0.3"
 
+react-native-drawer-layout@^3.2.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/react-native-drawer-layout/-/react-native-drawer-layout-3.2.0.tgz#1ab05d0bed6bb684353c17c96e1d3e6c1a4e225d"
+  integrity sha512-d/kvzeBhXjqcRGlfkTSB96ZRKH6g6YxJK+gPtUOCOCH5piHpsupgX+tLAHdM8r8NUzN6Tl9656xfJTuVb8Zrgw==
+  dependencies:
+    use-latest-callback "^0.1.5"
+
 react-native-fast-image@^8.6.3:
   version "8.6.3"
   resolved "https://registry.yarnpkg.com/react-native-fast-image/-/react-native-fast-image-8.6.3.tgz#6edc3f9190092a909d636d93eecbcc54a8822255"
@@ -11505,7 +13572,7 @@ react-native-fs@^2.20.0:
     base-64 "^0.1.0"
     utf8 "^3.0.0"
 
-react-native-gesture-handler@^2.5.0:
+react-native-gesture-handler@~2.9.0:
   version "2.9.0"
   resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-2.9.0.tgz#2f63812e523c646f25b9ad660fc6f75948e51241"
   integrity sha512-a0BcH3Qb1tgVqUutc6d3VuWQkI1AM3+fJx8dkxzZs9t06qA27QgURYFoklpabuWpsUTzuKRpxleykp25E8m7tg==
@@ -11516,10 +13583,10 @@ react-native-gesture-handler@^2.5.0:
     lodash "^4.17.21"
     prop-types "^15.7.2"
 
-react-native-gradle-plugin@^0.71.13:
-  version "0.71.15"
-  resolved "https://registry.yarnpkg.com/react-native-gradle-plugin/-/react-native-gradle-plugin-0.71.15.tgz#9e6b506f30729fe8eb086981702f4e3c891d2b13"
-  integrity sha512-7S3pAuPaQJlhax6EZ4JMsDNpj05TfuzX9gPgWLrFfAIWIFLuJ6aDQYAZy2TEI9QJALPoWrj8LWaqP/DGYh14pw==
+react-native-gradle-plugin@^0.71.15:
+  version "0.71.16"
+  resolved "https://registry.yarnpkg.com/react-native-gradle-plugin/-/react-native-gradle-plugin-0.71.16.tgz#822bb0c680e03b5df5aa65f2e5ffc2bc2930854a"
+  integrity sha512-H2BjG2zk7B7Wii9sXvd9qhCVRQYDAHSWdMw9tscmZBqSP62DkIWEQSk4/B2GhQ4aK9ydVXgtqR6tBeg3yy8TSA==
 
 react-native-haptic-feedback@^1.14.0:
   version "1.14.0"
@@ -11544,27 +13611,13 @@ react-native-linear-gradient@^2.6.2:
   resolved "https://registry.yarnpkg.com/react-native-linear-gradient/-/react-native-linear-gradient-2.6.2.tgz#56598a76832724b2afa7889747635b5c80948f38"
   integrity sha512-Z8Xxvupsex+9BBFoSYS87bilNPWcRfRsGC0cpJk72Nxb5p2nEkGSBv73xZbEHnW2mUFvP+huYxrVvjZkr/gRjQ==
 
-react-native-pager-view@^6.0.2:
-  version "6.1.4"
-  resolved "https://registry.yarnpkg.com/react-native-pager-view/-/react-native-pager-view-6.1.4.tgz#3a63ebd1b72f81991157ea552bb9c887e529bc8c"
-  integrity sha512-fmTwgGwPxGCBusKAq7gHzm+s1Yp0qh5rKPoQszaCuxrb+76KgK4Qe82jJNPUp2xTZOKSw+FbJU2QahF8ncTl+w==
-
-react-native-permissions@^3.6.1:
-  version "3.7.2"
-  resolved "https://registry.yarnpkg.com/react-native-permissions/-/react-native-permissions-3.7.2.tgz#445b3329d66d074fa88f5319b38717e5831b8fee"
-  integrity sha512-hoiygypCvN2KgDexrxX3cEpYb8BZWAv1783dR0ew5CRdLu2LUb4evJmz6enHUqhOutdQOUHMhJhX5M9X8DYYfg==
-  dependencies:
-    picocolors "^1.0.0"
-    read-pkg "^5.2.0"
-
-react-native-progress@^5.0.0:
+react-native-progress@bluesky-social/react-native-progress:
   version "5.0.0"
-  resolved "https://registry.yarnpkg.com/react-native-progress/-/react-native-progress-5.0.0.tgz#f5ac6ceaeee27f184c660b00f29419e82a9d0ab0"
-  integrity sha512-KjnGIt3r9i5Kn2biOD9fXLJocf0bwxPRxOyAgXEnZTJQU2O+HyzgGFRCbM5h3izm9kKIkSc1txh8aGmMafCD9A==
+  resolved "https://codeload.github.com/bluesky-social/react-native-progress/tar.gz/5a372f4f2ce5feb26f4f47b6a4d187ab9b923ab4"
   dependencies:
     prop-types "^15.7.2"
 
-react-native-reanimated@^2.9.1:
+react-native-reanimated@~2.14.4:
   version "2.14.4"
   resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-2.14.4.tgz#3fa3da4e7b99f5dfb28f86bcf24d9d1024d38836"
   integrity sha512-DquSbl7P8j4SAmc+kRdd75Ianm8G+IYQ9T4AQ6lrpLVeDkhZmjWI0wkutKWnp6L7c5XNVUrFDUf69dwETLCItQ==
@@ -11577,20 +13630,11 @@ react-native-reanimated@^2.9.1:
     setimmediate "^1.0.5"
     string-hash-64 "^1.0.3"
 
-react-native-root-siblings@^4.0.0, react-native-root-siblings@^4.1.1:
+react-native-root-siblings@^4.1.1:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/react-native-root-siblings/-/react-native-root-siblings-4.1.1.tgz#b7742db7634a87f507eb99a5fd699c4f10c46ab0"
   integrity sha512-sdmLElNs5PDWqmZmj4/aNH4anyxreaPm61c4ZkRiR8SO/GzLg6KjAbb0e17RmMdnBdD0AIQbS38h/l55YKN4ZA==
 
-react-native-root-toast@^3.4.0:
-  version "3.4.1"
-  resolved "https://registry.yarnpkg.com/react-native-root-toast/-/react-native-root-toast-3.4.1.tgz#8832dcf9d7f6296f689b2c02dd6cab125d9aa040"
-  integrity sha512-rZFoaVZWZiPCcwP9n5a+1KtVM22JNp6pkodluCoRIb5oE2ip8mhpdvlZUIWoM5nxfmL4TBThWDp7se6j4X7lPA==
-  dependencies:
-    deprecated-react-native-prop-types "^2.3.0"
-    prop-types "^15.5.10"
-    react-native-root-siblings "^4.0.0"
-
 react-native-safe-area-context@^4.4.1:
   version "4.5.0"
   resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-4.5.0.tgz#9208313236e8f49e1920ac1e2a2c975f03aed284"
@@ -11609,18 +13653,18 @@ react-native-splash-screen@^3.3.0:
   resolved "https://registry.yarnpkg.com/react-native-splash-screen/-/react-native-splash-screen-3.3.0.tgz#3af71ed17afe50fee69590a45aec399d071ead02"
   integrity sha512-rGjt6HkoSXxMqH4SQUJ1gnPQlPJV8+J47+4yhgTIan4bVvAwJhEeJH7wWt9hXSdH4+VfwTS0GTaflj1Tw83IhA==
 
-react-native-svg@^12.4.0:
-  version "12.5.1"
-  resolved "https://registry.yarnpkg.com/react-native-svg/-/react-native-svg-12.5.1.tgz#7dfd9daf2f8ed7843c0f3e7a16af193bd5f9b287"
-  integrity sha512-c374ENsq2MWCfr+7jC7TGwSeOAuC1Dp0osh2pw8PjpYFxmmB/toFIwcnCLz+SgBd6iLJClRhbATealqM05HOGg==
+react-native-svg@^13.4.0:
+  version "13.8.0"
+  resolved "https://registry.yarnpkg.com/react-native-svg/-/react-native-svg-13.8.0.tgz#b6a22cf77f8098f910490a13aeb160a37e182f97"
+  integrity sha512-G8Mx6W86da+vFimZBJvA93POw8yz0fgDS5biy6oIjMWVJVQSDzCyzwO/zY0yuZmCDhKSZzogl5m0wXXvW2OcTA==
   dependencies:
     css-select "^5.1.0"
     css-tree "^1.1.3"
 
 react-native-tab-view@^3.3.0:
-  version "3.4.1"
-  resolved "https://registry.yarnpkg.com/react-native-tab-view/-/react-native-tab-view-3.4.1.tgz#50342777633b54822f0e905aaf5852bf5b256c81"
-  integrity sha512-pjhcm9bml91tbO7GkKQ5FGrwlHJXQqMzLcZtkXOSfFxoJ2XHJvbLxQSK0MpLyOjhbFdVbeeG4Zt083XQB1c1Lw==
+  version "3.5.1"
+  resolved "https://registry.yarnpkg.com/react-native-tab-view/-/react-native-tab-view-3.5.1.tgz#2ad454afc0e186b43ea8b89053f39180d480d48b"
+  integrity sha512-qdrS5t+AEhfuKQyuCXkwHu4IVppkuTvzWWlkSZKrPaSkjjIa32xrsGxt1UW9YDdro2w4AMw5hKn1hFmg/5mvzA==
   dependencies:
     use-latest-callback "^0.1.5"
 
@@ -11666,10 +13710,10 @@ react-native-web@^0.18.11:
     postcss-value-parser "^4.2.0"
     styleq "^0.1.2"
 
-react-native-webview@^11.26.1:
-  version "11.26.1"
-  resolved "https://registry.yarnpkg.com/react-native-webview/-/react-native-webview-11.26.1.tgz#658c09ed5162dc170b361e48c2dd26c9712879da"
-  integrity sha512-hC7BkxOpf+z0UKhxFSFTPAM4shQzYmZHoELa6/8a/MspcjEP7ukYKpuSUTLDywQditT8yI9idfcKvfZDKQExGw==
+react-native-webview@11.26.0:
+  version "11.26.0"
+  resolved "https://registry.yarnpkg.com/react-native-webview/-/react-native-webview-11.26.0.tgz#e524992876fe4a79e69905f0fab8949b470e9f16"
+  integrity sha512-4T4CKRm8xlaQDz9h/bCMPGAvtkesrhkRWqCX9FDJEzBToaVUIsV0ZOqtC4w/JSnCtFKKYiaC1ReJtCGv+4mFeQ==
   dependencies:
     escape-string-regexp "2.0.0"
     invariant "2.2.4"
@@ -11681,10 +13725,10 @@ react-native-youtube-iframe@^2.2.2:
   dependencies:
     events "^3.2.0"
 
-react-native@0.71.1:
-  version "0.71.1"
-  resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.71.1.tgz#72b45af2b29e3d5a660c63425ab5003bf2112f99"
-  integrity sha512-bLP5+IBj2IX6tgF9WnC/UL2ZPYkVUPsU4xqZV1jntTC2TH4xyLrvfKACjGlz5nQ3Mx4BmOFqsnMxithm53+6Aw==
+react-native@0.71.3:
+  version "0.71.3"
+  resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.71.3.tgz#0faab799c49e61ba12df9e6525c3ac7d595d673c"
+  integrity sha512-RYJXCcQGa4NTfKiPgl92eRDUuQ6JGDnHqFEzRwJSqEx9lWvlvRRIebstJfurzPDKLQWQrvITR7aI7e09E25mLw==
   dependencies:
     "@jest/create-cache-key-function" "^29.2.1"
     "@react-native-community/cli" "10.1.3"
@@ -11700,7 +13744,7 @@ react-native@0.71.1:
     event-target-shim "^5.0.1"
     invariant "^2.2.4"
     jest-environment-node "^29.2.1"
-    jsc-android "^250230.2.1"
+    jsc-android "^250231.0.0"
     memoize-one "^5.0.0"
     metro-react-native-babel-transformer "0.73.7"
     metro-runtime "0.73.7"
@@ -11710,8 +13754,8 @@ react-native@0.71.1:
     pretty-format "^26.5.2"
     promise "^8.3.0"
     react-devtools-core "^4.26.1"
-    react-native-codegen "^0.71.3"
-    react-native-gradle-plugin "^0.71.13"
+    react-native-codegen "^0.71.5"
+    react-native-gradle-plugin "^0.71.15"
     react-refresh "^0.4.0"
     react-shallow-renderer "^16.15.0"
     regenerator-runtime "^0.13.2"
@@ -11817,16 +13861,6 @@ read-cache@^1.0.0:
   dependencies:
     pify "^2.3.0"
 
-read-pkg@^5.2.0:
-  version "5.2.0"
-  resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc"
-  integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==
-  dependencies:
-    "@types/normalize-package-data" "^2.4.0"
-    normalize-package-data "^2.5.0"
-    parse-json "^5.0.0"
-    type-fest "^0.6.0"
-
 readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@~2.3.6:
   version "2.3.8"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b"
@@ -11978,6 +14012,21 @@ regexpu-core@^5.3.1:
     unicode-match-property-ecmascript "^2.0.0"
     unicode-match-property-value-ecmascript "^2.1.0"
 
+registry-auth-token@3.3.2:
+  version "3.3.2"
+  resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.3.2.tgz#851fd49038eecb586911115af845260eec983f20"
+  integrity sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==
+  dependencies:
+    rc "^1.1.6"
+    safe-buffer "^5.0.1"
+
+registry-url@3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942"
+  integrity sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==
+  dependencies:
+    rc "^1.0.1"
+
 regjsparser@^0.9.1:
   version "0.9.1"
   resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.9.1.tgz#272d05aa10c7c1f67095b1ff0addae8442fc5709"
@@ -11990,6 +14039,11 @@ relateurl@^0.2.7:
   resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"
   integrity sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==
 
+remove-trailing-slash@^0.1.0:
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/remove-trailing-slash/-/remove-trailing-slash-0.1.1.tgz#be2285a59f39c74d1bce4f825950061915e3780d"
+  integrity sha512-o4S4Qh6L2jpnCy83ysZDau+VORNvnFw07CKSAymkd6ICNVEPisMyzlc00KlvvicsxKck94SEwhDnMNdICzO+tA==
+
 renderkid@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-3.0.0.tgz#5fd823e4d6951d37358ecc9a58b1f06836b6268a"
@@ -12026,6 +14080,15 @@ require-main-filename@^2.0.0:
   resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
   integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
 
+requireg@^0.2.2:
+  version "0.2.2"
+  resolved "https://registry.yarnpkg.com/requireg/-/requireg-0.2.2.tgz#437e77a5316a54c9bcdbbf5d1f755fe093089830"
+  integrity sha512-nYzyjnFcPNGR3lx9lwPPPnuQxv6JWEZd2Ci0u9opN7N5zUEPIhY/GbL3vMGOr2UXwEg9WwSyV9X9Y/kLFgPsOg==
+  dependencies:
+    nested-error-stacks "~2.0.1"
+    rc "~1.2.7"
+    resolve "~1.7.1"
+
 requireindex@~1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/requireindex/-/requireindex-1.1.0.tgz#e5404b81557ef75db6e49c5a72004893fe03e162"
@@ -12036,7 +14099,7 @@ requires-port@^1.0.0:
   resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
   integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==
 
-reselect@^4.1.7:
+reselect@^4.0.0, reselect@^4.1.7:
   version "4.1.7"
   resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.7.tgz#56480d9ff3d3188970ee2b76527bd94a95567a42"
   integrity sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A==
@@ -12085,11 +14148,11 @@ resolve.exports@^1.1.0:
   integrity sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ==
 
 resolve.exports@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.0.tgz#c1a0028c2d166ec2fbf7d0644584927e76e7400e"
-  integrity sha512-6K/gDlqgQscOlg9fSRpWstA8sYe8rbELsSTNpx+3kTrsVCzvSl0zIvRErM7fdl9ERWDsKnrLnwB+Ne89918XOg==
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.1.tgz#cee884cd4e3f355660e501fa3276b27d7ffe5a20"
+  integrity sha512-OEJWVeimw8mgQuj3HfkNl4KqRevH7lzeQNaWRPfx0PPse7Jk6ozcsG4FKVgtzDsC1KUF+YlTHh17NcgHOPykLw==
 
-resolve@^1.1.7, resolve@^1.10.0, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.1:
+resolve@^1.1.7, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.1:
   version "1.22.1"
   resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
   integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==
@@ -12107,6 +14170,21 @@ resolve@^2.0.0-next.4:
     path-parse "^1.0.7"
     supports-preserve-symlinks-flag "^1.0.0"
 
+resolve@~1.7.1:
+  version "1.7.1"
+  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3"
+  integrity sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==
+  dependencies:
+    path-parse "^1.0.5"
+
+restore-cursor@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf"
+  integrity sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==
+  dependencies:
+    onetime "^2.0.0"
+    signal-exit "^3.0.2"
+
 restore-cursor@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e"
@@ -12140,6 +14218,13 @@ rfdc@^1.2.0:
   resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b"
   integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==
 
+rimraf@^2.6.2, rimraf@^2.6.3:
+  version "2.7.1"
+  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
+  integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
+  dependencies:
+    glob "^7.1.3"
+
 rimraf@^3.0.0, rimraf@^3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
@@ -12203,6 +14288,11 @@ rollup@^2.43.1:
   optionalDependencies:
     fsevents "~2.3.2"
 
+rope-sequence@^1.3.0:
+  version "1.3.3"
+  resolved "https://registry.yarnpkg.com/rope-sequence/-/rope-sequence-1.3.3.tgz#3f67fc106288b84b71532b4a5fd9d4881e4457f0"
+  integrity sha512-85aZYCxweiD5J8yTEbw+E6A27zSnLPNDL0WfPdw3YYodq7WjnTKo0q4dtyQ2gz23iPT8Q9CUyJtAaUNcTxRf5Q==
+
 run-parallel@^1.1.9:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
@@ -12278,7 +14368,7 @@ sass-loader@^12.3.0:
     klona "^2.0.4"
     neo-async "^2.6.2"
 
-sax@~1.2.4:
+sax@>=0.6.0, sax@^1.2.4, sax@~1.2.4:
   version "1.2.4"
   resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
   integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
@@ -12290,6 +14380,13 @@ saxes@^5.0.1:
   dependencies:
     xmlchars "^2.2.0"
 
+saxes@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/saxes/-/saxes-6.0.0.tgz#fe5b4a4768df4f14a201b1ba6a65c1f3d9988cc5"
+  integrity sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==
+  dependencies:
+    xmlchars "^2.2.0"
+
 scheduler@^0.23.0:
   version "0.23.0"
   resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe"
@@ -12351,24 +14448,29 @@ semver-compare@^1.0.0:
   resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
   integrity sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==
 
-"semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.6.0:
-  version "5.7.1"
-  resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
-  integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
+semver@7.3.2:
+  version "7.3.2"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938"
+  integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==
 
-semver@7.3.8, semver@^7.0.0, semver@^7.3.2, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8:
+semver@7.3.8, semver@^7.0.0, semver@^7.3.2, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@~7.3.2:
   version "7.3.8"
   resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798"
   integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==
   dependencies:
     lru-cache "^6.0.0"
 
+semver@^5.5.0, semver@^5.6.0:
+  version "5.7.1"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
+  integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
+
 semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0:
   version "6.3.0"
   resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
   integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
 
-send@0.18.0:
+send@0.18.0, send@^0.18.0:
   version "0.18.0"
   resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be"
   integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==
@@ -12387,6 +14489,13 @@ send@0.18.0:
     range-parser "~1.2.1"
     statuses "2.0.1"
 
+serialize-error@6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-6.0.0.tgz#ccfb887a1dd1c48d6d52d7863b92544331fd752b"
+  integrity sha512-3vmBkMZLQO+BR4RPHcyRGdE09XCF6cvxzk2N2qn8Er3F91cy8Qt7VvEbZBOpaL53qsBbe2cFOefU6tRY6WDelA==
+  dependencies:
+    type-fest "^0.12.0"
+
 serialize-error@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-2.1.0.tgz#50b679d5635cdf84667bdc8e59af4e5b81d5f60a"
@@ -12406,7 +14515,7 @@ serialize-javascript@^4.0.0:
   dependencies:
     randombytes "^2.1.0"
 
-serialize-javascript@^6.0.0:
+serialize-javascript@^6.0.0, serialize-javascript@^6.0.1:
   version "6.0.1"
   resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.1.tgz#b206efb27c3da0b0ab6b52f48d170b7996458e5c"
   integrity sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==
@@ -12549,6 +14658,15 @@ simple-get@^4.0.0, simple-get@^4.0.1:
     once "^1.3.1"
     simple-concat "^1.0.0"
 
+simple-plist@^1.1.0:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/simple-plist/-/simple-plist-1.3.1.tgz#16e1d8f62c6c9b691b8383127663d834112fb017"
+  integrity sha512-iMSw5i0XseMnrhtIzRb7XpQEXepa9xhWxGUojHBL43SIpQuDQkh3Wpy67ZbDzZVr6EKxvwVChnVpdl8hEVLDiw==
+  dependencies:
+    bplist-creator "0.1.0"
+    bplist-parser "0.3.1"
+    plist "^3.0.5"
+
 simple-swizzle@^0.2.2:
   version "0.2.2"
   resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a"
@@ -12571,6 +14689,11 @@ slash@^4.0.0:
   resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7"
   integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==
 
+slash@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/slash/-/slash-5.0.0.tgz#8c18a871096b71ee0e002976a4fe3374991c3074"
+  integrity sha512-n6KkmvKS0623igEVj3FF0OZs1gYYJ0o0Hj939yc1fyxl2xt+xYpLnzJB6xBSqOfV9ZFLEWodBBN/heZJahuIJQ==
+
 slice-ansi@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636"
@@ -12580,6 +14703,11 @@ slice-ansi@^2.0.0:
     astral-regex "^1.0.0"
     is-fullwidth-code-point "^2.0.0"
 
+slugify@^1.3.4:
+  version "1.6.5"
+  resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.6.5.tgz#c8f5c072bf2135b80703589b39a3d41451fbe8c8"
+  integrity sha512-8mo9bslnBO3tr5PEVFzMPIWwWnipGS0xVbYf65zxDqfNwmzYn1LpiKNrR6DlClusuvo+hDHd1zKpmfAe83NQSQ==
+
 snapdragon-node@^2.0.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b"
@@ -12636,7 +14764,7 @@ source-map-js@^1.0.1, source-map-js@^1.0.2:
   resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
   integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
 
-source-map-loader@^3.0.0:
+source-map-loader@^3.0.0, source-map-loader@^3.0.1:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-3.0.2.tgz#af23192f9b344daa729f6772933194cc5fa54fee"
   integrity sha512-BokxPoLjyl3iOrgkWaakaxqnelAJSS+0V+De0kKIq6lyWrXuiPgYTGp6z3iHmqljKAaLXwZa+ctD8GccRJeVvg==
@@ -12704,32 +14832,6 @@ sourcemap-codec@^1.4.8:
   resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
   integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
 
-spdx-correct@^3.0.0:
-  version "3.1.1"
-  resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9"
-  integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==
-  dependencies:
-    spdx-expression-parse "^3.0.0"
-    spdx-license-ids "^3.0.0"
-
-spdx-exceptions@^2.1.0:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d"
-  integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==
-
-spdx-expression-parse@^3.0.0:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679"
-  integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==
-  dependencies:
-    spdx-exceptions "^2.1.0"
-    spdx-license-ids "^3.0.0"
-
-spdx-license-ids@^3.0.0:
-  version "3.0.12"
-  resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz#69077835abe2710b65f03969898b6637b505a779"
-  integrity sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==
-
 spdy-transport@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31"
@@ -12753,6 +14855,11 @@ spdy@^4.0.2:
     select-hose "^2.0.0"
     spdy-transport "^3.0.0"
 
+split-on-first@^1.0.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f"
+  integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==
+
 split-string@^3.0.1, split-string@^3.0.2:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2"
@@ -12765,11 +14872,25 @@ split2@^4.0.0, split2@^4.1.0:
   resolved "https://registry.yarnpkg.com/split2/-/split2-4.1.0.tgz#101907a24370f85bb782f08adaabe4e281ecf809"
   integrity sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==
 
+split@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9"
+  integrity sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==
+  dependencies:
+    through "2"
+
 sprintf-js@~1.0.2:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
   integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==
 
+ssri@^8.0.1:
+  version "8.0.1"
+  resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af"
+  integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==
+  dependencies:
+    minipass "^3.1.1"
+
 stable@^0.1.8:
   version "0.1.8"
   resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf"
@@ -12819,6 +14940,11 @@ stop-iteration-iterator@^1.0.0:
   dependencies:
     internal-slot "^1.0.4"
 
+stream-buffers@2.2.x:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/stream-buffers/-/stream-buffers-2.2.0.tgz#91d5f5130d1cef96dcfa7f726945188741d09ee4"
+  integrity sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg==
+
 stream-chain@^2.2.5:
   version "2.2.5"
   resolved "https://registry.yarnpkg.com/stream-chain/-/stream-chain-2.2.5.tgz#b30967e8f14ee033c5b9a19bbe8a2cba90ba0d09"
@@ -12831,6 +14957,11 @@ stream-json@^1.7.4:
   dependencies:
     stream-chain "^2.2.5"
 
+strict-uri-encode@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546"
+  integrity sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==
+
 string-hash-64@^1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/string-hash-64/-/string-hash-64-1.0.3.tgz#0deb56df58678640db5c479ccbbb597aaa0de322"
@@ -13002,6 +15133,11 @@ strtok3@^6.2.4:
     "@tokenizer/token" "^0.3.0"
     peek-readable "^4.1.0"
 
+structured-headers@^0.4.1:
+  version "0.4.1"
+  resolved "https://registry.yarnpkg.com/structured-headers/-/structured-headers-0.4.1.tgz#77abd9410622c6926261c09b9d16cf10592694d1"
+  integrity sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg==
+
 style-loader@^3.3.1:
   version "3.3.1"
   resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.1.tgz#057dfa6b3d4d7c7064462830f9113ed417d38575"
@@ -13020,6 +15156,28 @@ styleq@^0.1.2:
   resolved "https://registry.yarnpkg.com/styleq/-/styleq-0.1.3.tgz#8efb2892debd51ce7b31dc09c227ad920decab71"
   integrity sha512-3ZUifmCDCQanjeej1f6kyl/BeP/Vae5EYkQ9iJfUm/QwZvlgnZzyflqAsAWYURdtea8Vkvswu2GrC57h3qffcA==
 
+sucrase@^3.20.0:
+  version "3.29.0"
+  resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.29.0.tgz#3207c5bc1b980fdae1e539df3f8a8a518236da7d"
+  integrity sha512-bZPAuGA5SdFHuzqIhTAqt9fvNEo9rESqXIG3oiKdF8K4UmkQxC4KlNL3lVyAErXp+mPvUqZ5l13qx6TrDIGf3A==
+  dependencies:
+    commander "^4.0.0"
+    glob "7.1.6"
+    lines-and-columns "^1.1.6"
+    mz "^2.7.0"
+    pirates "^4.0.1"
+    ts-interface-checker "^0.1.9"
+
+sudo-prompt@9.1.1:
+  version "9.1.1"
+  resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-9.1.1.tgz#73853d729770392caec029e2470db9c221754db0"
+  integrity sha512-es33J1g2HjMpyAhz8lOR+ICmXXAqTuKbuXuUWLhOLew20oN9oUCgCJx615U/v7aioZg7IX5lIh9x34vwneu4pA==
+
+sudo-prompt@^8.2.0:
+  version "8.2.5"
+  resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-8.2.5.tgz#cc5ef3769a134bb94b24a631cc09628d4d53603e"
+  integrity sha512-rlBo3HU/1zAJUrkY6jNxDOC9eVYliG6nS4JA8u8KAshITd07tafMc/Br7xQwCSseXwJ2iCcHCE8SNWX3q8Z+kw==
+
 sudo-prompt@^9.0.0:
   version "9.2.1"
   resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-9.2.1.tgz#77efb84309c9ca489527a4e749f287e6bdd52afd"
@@ -13161,6 +15319,18 @@ tar-stream@^2.1.4:
     inherits "^2.0.3"
     readable-stream "^3.1.1"
 
+tar@^6.0.2, tar@^6.0.5:
+  version "6.1.13"
+  resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.13.tgz#46e22529000f612180601a6fe0680e7da508847b"
+  integrity sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==
+  dependencies:
+    chownr "^2.0.0"
+    fs-minipass "^2.0.0"
+    minipass "^4.0.0"
+    minizlib "^2.1.1"
+    mkdirp "^1.0.3"
+    yallist "^4.0.0"
+
 telnet-client@1.2.8:
   version "1.2.8"
   resolved "https://registry.yarnpkg.com/telnet-client/-/telnet-client-1.2.8.tgz#946c0dadc8daa3f19bb40a3e898cb870403a4ca4"
@@ -13201,6 +15371,15 @@ tempfile@^2.0.0:
     temp-dir "^1.0.0"
     uuid "^3.0.1"
 
+tempy@0.3.0:
+  version "0.3.0"
+  resolved "https://registry.yarnpkg.com/tempy/-/tempy-0.3.0.tgz#6f6c5b295695a16130996ad5ab01a8bd726e8bf8"
+  integrity sha512-WrH/pui8YCwmeiAoxV+lpRH9HpRtgBhSR2ViBPgpGb/wnYDzp21R4MN45fsCGvLROvY67o3byhJRYRONJyImVQ==
+  dependencies:
+    temp-dir "^1.0.0"
+    type-fest "^0.3.1"
+    unique-string "^1.0.0"
+
 tempy@^0.6.0:
   version "0.6.0"
   resolved "https://registry.yarnpkg.com/tempy/-/tempy-0.6.0.tgz#65e2c35abc06f1124a97f387b08303442bde59f3"
@@ -13211,7 +15390,18 @@ tempy@^0.6.0:
     type-fest "^0.16.0"
     unique-string "^2.0.0"
 
-terminal-link@^2.0.0:
+tempy@^0.7.1:
+  version "0.7.1"
+  resolved "https://registry.yarnpkg.com/tempy/-/tempy-0.7.1.tgz#5a654e6dbd1747cdd561efb112350b55cd9c1d46"
+  integrity sha512-vXPxwOyaNVi9nyczO16mxmHGpl6ASC5/TVhRRHpqeYHvKQm58EaWNvZXxAhR0lYYnBOQFjXjhzeLsaXdjxLjRg==
+  dependencies:
+    del "^6.0.0"
+    is-stream "^2.0.0"
+    temp-dir "^2.0.0"
+    type-fest "^0.16.0"
+    unique-string "^2.0.0"
+
+terminal-link@^2.0.0, terminal-link@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994"
   integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==
@@ -13219,18 +15409,18 @@ terminal-link@^2.0.0:
     ansi-escapes "^4.2.1"
     supports-hyperlinks "^2.0.0"
 
-terser-webpack-plugin@^5.1.3, terser-webpack-plugin@^5.2.5:
-  version "5.3.6"
-  resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz#5590aec31aa3c6f771ce1b1acca60639eab3195c"
-  integrity sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==
+terser-webpack-plugin@^5.1.3, terser-webpack-plugin@^5.2.5, terser-webpack-plugin@^5.3.0:
+  version "5.3.7"
+  resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.7.tgz#ef760632d24991760f339fe9290deb936ad1ffc7"
+  integrity sha512-AfKwIktyP7Cu50xNjXF/6Qb5lBNzYaWpU6YfoX3uZicTx0zTy0stDDCsvjDapKsSDvOeWo5MEq4TmdBy2cNoHw==
   dependencies:
-    "@jridgewell/trace-mapping" "^0.3.14"
+    "@jridgewell/trace-mapping" "^0.3.17"
     jest-worker "^27.4.5"
     schema-utils "^3.1.1"
-    serialize-javascript "^6.0.0"
-    terser "^5.14.1"
+    serialize-javascript "^6.0.1"
+    terser "^5.16.5"
 
-terser@^5.0.0, terser@^5.10.0, terser@^5.14.1, terser@^5.15.0:
+terser@^5.0.0, terser@^5.10.0, terser@^5.15.0, terser@^5.16.5:
   version "5.16.5"
   resolved "https://registry.yarnpkg.com/terser/-/terser-5.16.5.tgz#1c285ca0655f467f92af1bbab46ab72d1cb08e5a"
   integrity sha512-qcwfg4+RZa3YvlFh0qjifnzBHjKGNbtDo9yivMqMFDy9Q6FSaQWSB/j1xKhsoUFJIqDOM3TsN6D5xbrMrFcHbg==
@@ -13254,6 +15444,20 @@ text-table@^0.2.0:
   resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
   integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
 
+thenify-all@^1.0.0:
+  version "1.6.0"
+  resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726"
+  integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==
+  dependencies:
+    thenify ">= 3.1.0 < 4"
+
+"thenify@>= 3.1.0 < 4":
+  version "3.3.1"
+  resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f"
+  integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==
+  dependencies:
+    any-promise "^1.0.0"
+
 thread-stream@^2.0.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-2.3.0.tgz#4fc07fb39eff32ae7bad803cb7dd9598349fed33"
@@ -13271,6 +15475,11 @@ throat@^6.0.1:
   resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.2.tgz#51a3fbb5e11ae72e2cf74861ed5c8020f89f29fe"
   integrity sha512-WKexMoJj3vEuK0yFEapj8y64V0A6xcuPuK9Gt1d0R+dzCSJc0lHqQytAbSB4cDAK0dWh4T0E2ETkoLE2WZ41OQ==
 
+throttle-debounce@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-3.0.1.tgz#32f94d84dfa894f786c9a1f290e7a645b6a19abb"
+  integrity sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==
+
 through2@^2.0.1:
   version "2.0.5"
   resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"
@@ -13279,15 +15488,34 @@ through2@^2.0.1:
     readable-stream "~2.3.6"
     xtend "~4.0.1"
 
+through@2:
+  version "2.3.8"
+  resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
+  integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==
+
 thunky@^1.0.2:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d"
   integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==
 
+tippy.js@^6.3.7:
+  version "6.3.7"
+  resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-6.3.7.tgz#8ccfb651d642010ed9a32ff29b0e9e19c5b8c61c"
+  integrity sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==
+  dependencies:
+    "@popperjs/core" "^2.9.0"
+
 tlds@^1.234.0:
-  version "1.236.0"
-  resolved "https://registry.yarnpkg.com/tlds/-/tlds-1.236.0.tgz#a118eebe33261c577e3a3025144faeabb7dd813c"
-  integrity sha512-oP2PZ3KeGlgpHgsEfrtva3/K9kzsJUNliQSbCfrJ7JMCWFoCdtG+9YMq/g2AnADQ1v5tVlbtvKJZ4KLpy/P6MA==
+  version "1.237.0"
+  resolved "https://registry.yarnpkg.com/tlds/-/tlds-1.237.0.tgz#71e5ca558878a046bc9e253db7a1f2b602ce1a2d"
+  integrity sha512-4IA6zR7jQop4pEdziQaptOgkIwnnZ537fXM3MKAzOXjXLjiHm77SA3/E0nXWJGSVRnKcn/JxDJmwTqyPgQ+ozg==
+
+tmp@^0.0.33:
+  version "0.0.33"
+  resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
+  integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==
+  dependencies:
+    os-tmpdir "~1.0.2"
 
 tmpl@1.0.5:
   version "1.0.5"
@@ -13344,7 +15572,7 @@ token-types@^4.1.1:
     "@tokenizer/token" "^0.3.0"
     ieee754 "^1.2.1"
 
-tough-cookie@^4.0.0:
+tough-cookie@^4.0.0, tough-cookie@^4.1.2:
   version "4.1.2"
   resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.2.tgz#e53e84b85f24e0b65dd526f46628db6c85f6b874"
   integrity sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==
@@ -13368,6 +15596,13 @@ tr46@^2.1.0:
   dependencies:
     punycode "^2.1.1"
 
+tr46@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9"
+  integrity sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==
+  dependencies:
+    punycode "^2.1.1"
+
 tr46@~0.0.3:
   version "0.0.3"
   resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
@@ -13381,6 +15616,11 @@ trace-event-lib@^1.3.1:
     browser-process-hrtime "^1.0.0"
     lodash "^4.17.21"
 
+traverse@~0.6.6:
+  version "0.6.7"
+  resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.7.tgz#46961cd2d57dd8706c36664acde06a248f1173fe"
+  integrity sha512-/y956gpUo9ZNCb99YjxG7OaslxZWHfCHAUUfshwqOXmxUIvqLjVO581BT+gM59+QV9tFe6/CGG53tsA1Y7RSdg==
+
 truncate-utf8-bytes@^1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz#405923909592d56f78a5818434b0b78489ca5f2b"
@@ -13393,6 +15633,11 @@ tryer@^1.0.1:
   resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8"
   integrity sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==
 
+ts-interface-checker@^0.1.9:
+  version "0.1.13"
+  resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699"
+  integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==
+
 tsconfig-paths@^3.14.1:
   version "3.14.2"
   resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz#6e32f1f79412decd261f92d633a9dc1cfa99f088"
@@ -13446,6 +15691,11 @@ type-detect@4.0.8:
   resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
   integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
 
+type-fest@^0.12.0:
+  version "0.12.0"
+  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.12.0.tgz#f57a27ab81c68d136a51fd71467eff94157fa1ee"
+  integrity sha512-53RyidyjvkGpnWPMF9bQgFtWp+Sl8O2Rp13VavmJgfAP9WWG6q6TkrKU8iyJdnwnfgHI6k2hTlgqH4aSdjoTbg==
+
 type-fest@^0.16.0:
   version "0.16.0"
   resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.16.0.tgz#3240b891a78b0deae910dbeb86553e552a148860"
@@ -13461,21 +15711,26 @@ type-fest@^0.21.3:
   resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37"
   integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==
 
-type-fest@^0.6.0:
-  version "0.6.0"
-  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b"
-  integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==
+type-fest@^0.3.1:
+  version "0.3.1"
+  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.3.1.tgz#63d00d204e059474fe5e1b7c011112bbd1dc29e1"
+  integrity sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==
 
 type-fest@^0.7.1:
   version "0.7.1"
   resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.7.1.tgz#8dda65feaf03ed78f0a3f9678f1869147f7c5c48"
   integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==
 
-type-fest@^2.3.3:
+type-fest@^2.0.0, type-fest@^2.3.3:
   version "2.19.0"
   resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b"
   integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==
 
+type-fest@^3.0.0:
+  version "3.6.1"
+  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-3.6.1.tgz#cf8025edeebfd6cf48de73573a5e1423350b9993"
+  integrity sha512-htXWckxlT6U4+ilVgweNliPqlsVSSucbxVexRYllyMVJDtf5rTjv6kF/s+qAd4QSL1BZcnJPEJavYBPQiWuZDA==
+
 type-is@~1.6.18:
   version "1.6.18"
   resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
@@ -13513,9 +15768,14 @@ typescript@^4.4.4:
   integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==
 
 ua-parser-js@^0.7.30:
-  version "0.7.33"
-  resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.33.tgz#1d04acb4ccef9293df6f70f2c3d22f3030d8b532"
-  integrity sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw==
+  version "0.7.34"
+  resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.34.tgz#afb439e2e3e394bdc90080acb661a39c685b67d7"
+  integrity sha512-cJMeh/eOILyGu0ejgTKB95yKT3zOenSe9UGE3vj6WfiOwgGYnmATUsnDixMFvdU+rNMvWih83hrUP8VwhF9yXQ==
+
+uc.micro@^1.0.1, uc.micro@^1.0.5:
+  version "1.0.6"
+  resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac"
+  integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==
 
 uglify-es@^3.1.9:
   version "3.3.9"
@@ -13580,6 +15840,27 @@ union-value@^1.0.0:
     is-extendable "^0.1.1"
     set-value "^2.0.1"
 
+unique-filename@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230"
+  integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==
+  dependencies:
+    unique-slug "^2.0.0"
+
+unique-slug@^2.0.0:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c"
+  integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==
+  dependencies:
+    imurmurhash "^0.1.4"
+
+unique-string@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-1.0.0.tgz#9e1057cca851abb93398f8b33ae187b99caec11a"
+  integrity sha512-ODgiYu03y5g76A1I9Gt0/chLCzQjvzDy7DsZGsLOE/1MrF6wriEskSncj1+/C58Xk/kPZDppSctDybCwOSaGAg==
+  dependencies:
+    crypto-random-string "^1.0.0"
+
 unique-string@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d"
@@ -13597,6 +15878,11 @@ universalify@^0.2.0:
   resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0"
   integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==
 
+universalify@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d"
+  integrity sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==
+
 universalify@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717"
@@ -13633,6 +15919,14 @@ update-browserslist-db@^1.0.10:
     escalade "^3.1.1"
     picocolors "^1.0.0"
 
+update-check@1.5.3:
+  version "1.5.3"
+  resolved "https://registry.yarnpkg.com/update-check/-/update-check-1.5.3.tgz#45240fcfb8755a7c7fa68bbdd9eda026a41639ed"
+  integrity sha512-6KLU4/dd0Tg/l0xwL+f9V7kEIPSL1vOIbnNnhSLiRDlj4AVG6Ks9Zoc9Jgt9kIgWFPZ/wp2AHgmG7xNf15TJOA==
+  dependencies:
+    registry-auth-token "3.3.2"
+    registry-url "3.1.0"
+
 uri-js@^4.2.2:
   version "4.4.1"
   resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
@@ -13645,6 +15939,11 @@ urix@^0.1.0:
   resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
   integrity sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==
 
+url-join@4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.0.tgz#4d3340e807d3773bda9991f8305acdcc2a665d2a"
+  integrity sha512-EGXjXJZhIHiQMK2pQukuFcL303nskqIRzWvPvV5O8miOfwoUb9G+a/Cld60kUyeaybEI94wvVClT10DtfeAExA==
+
 url-loader@^4.1.1:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-4.1.1.tgz#28505e905cae158cf07c92ca622d7f237e70a4e2"
@@ -13654,7 +15953,7 @@ url-loader@^4.1.1:
     mime-types "^2.1.27"
     schema-utils "^3.0.0"
 
-url-parse@^1.5.3:
+url-parse@^1.5.3, url-parse@^1.5.9:
   version "1.5.10"
   resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1"
   integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==
@@ -13712,12 +16011,17 @@ utils-merge@1.0.1:
   resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
   integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
 
-uuid@^3.0.1:
+uuid@^3.0.1, uuid@^3.3.2, uuid@^3.4.0:
   version "3.4.0"
   resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
   integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
 
-uuid@^8.3.2:
+uuid@^7.0.3:
+  version "7.0.3"
+  resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b"
+  integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==
+
+uuid@^8.0.0, uuid@^8.3.2:
   version "8.3.2"
   resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
   integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
@@ -13740,13 +16044,17 @@ v8-to-istanbul@^9.0.1:
     "@types/istanbul-lib-coverage" "^2.0.1"
     convert-source-map "^1.6.0"
 
-validate-npm-package-license@^3.0.1:
-  version "3.0.4"
-  resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
-  integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==
+valid-url@~1.0.9:
+  version "1.0.9"
+  resolved "https://registry.yarnpkg.com/valid-url/-/valid-url-1.0.9.tgz#1c14479b40f1397a75782f115e4086447433a200"
+  integrity sha512-QQDsV8OnSf5Uc30CKSwG9lnhMPe6exHtTXLRYX8uMwKENy640pU+2BgBL0LRbDh/eYRahNCS7aewCx0wf3NYVA==
+
+validate-npm-package-name@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz#5fa912d81eb7d0c74afc140de7317f0ca7df437e"
+  integrity sha512-M6w37eVCMMouJ9V/sdPGnC5H4uDr73/+xdq0FBLO3TFFX1+7wiUY6Es328NN+y43tmY+doUdN9g9J21vqB7iLw==
   dependencies:
-    spdx-correct "^3.0.0"
-    spdx-expression-parse "^3.0.0"
+    builtins "^1.0.3"
 
 varint@^6.0.0:
   version "6.0.0"
@@ -13770,6 +16078,11 @@ w3c-hr-time@^1.0.2:
   dependencies:
     browser-process-hrtime "^1.0.0"
 
+w3c-keyname@^2.2.0:
+  version "2.2.6"
+  resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-2.2.6.tgz#8412046116bc16c5d73d4e612053ea10a189c85f"
+  integrity sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg==
+
 w3c-xmlserializer@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz#3e7104a05b75146cc60f564380b7f683acf1020a"
@@ -13777,6 +16090,13 @@ w3c-xmlserializer@^2.0.0:
   dependencies:
     xml-name-validator "^3.0.0"
 
+w3c-xmlserializer@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz#aebdc84920d806222936e3cdce408e32488a3073"
+  integrity sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==
+  dependencies:
+    xml-name-validator "^4.0.0"
+
 walker@^1.0.7, walker@^1.0.8:
   version "1.0.8"
   resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f"
@@ -13831,6 +16151,11 @@ webidl-conversions@^6.1.0:
   resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514"
   integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==
 
+webidl-conversions@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a"
+  integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==
+
 webpack-cli@^5.0.1:
   version "5.0.1"
   resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-5.0.1.tgz#95fc0495ac4065e9423a722dec9175560b6f2d9a"
@@ -13896,7 +16221,7 @@ webpack-dev-server@^4.11.1, webpack-dev-server@^4.6.0:
     webpack-dev-middleware "^5.3.1"
     ws "^8.4.2"
 
-webpack-manifest-plugin@^4.0.2:
+webpack-manifest-plugin@^4.0.2, webpack-manifest-plugin@^4.1.1:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/webpack-manifest-plugin/-/webpack-manifest-plugin-4.1.1.tgz#10f8dbf4714ff93a215d5a45bcc416d80506f94f"
   integrity sha512-YXUAwxtfKIJIKkhg03MKuiFAD72PlrqCiwdwO4VEXdRO5V0ORCNwaOwAZawPZalCbmH9kBDmXnNeQOw+BIEiow==
@@ -13934,9 +16259,9 @@ webpack-sources@^3.2.3:
   integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
 
 webpack@^5.64.4, webpack@^5.75.0:
-  version "5.75.0"
-  resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.75.0.tgz#1e440468647b2505860e94c9ff3e44d5b582c152"
-  integrity sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==
+  version "5.76.0"
+  resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.76.0.tgz#f9fb9fb8c4a7dbdcd0d56a98e56b8a942ee2692c"
+  integrity sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==
   dependencies:
     "@types/eslint-scope" "^3.7.3"
     "@types/estree" "^0.0.51"
@@ -13984,6 +16309,13 @@ whatwg-encoding@^1.0.5:
   dependencies:
     iconv-lite "0.4.24"
 
+whatwg-encoding@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53"
+  integrity sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==
+  dependencies:
+    iconv-lite "0.6.3"
+
 whatwg-fetch@^3.0.0, whatwg-fetch@^3.6.2:
   version "3.6.2"
   resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz#dced24f37f2624ed0281725d51d0e2e3fe677f8c"
@@ -13994,6 +16326,11 @@ whatwg-mimetype@^2.3.0:
   resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf"
   integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==
 
+whatwg-mimetype@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7"
+  integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==
+
 whatwg-url-without-unicode@8.0.0-3:
   version "8.0.0-3"
   resolved "https://registry.yarnpkg.com/whatwg-url-without-unicode/-/whatwg-url-without-unicode-8.0.0-3.tgz#ab6df4bf6caaa6c85a59f6e82c026151d4bb376b"
@@ -14003,6 +16340,14 @@ whatwg-url-without-unicode@8.0.0-3:
     punycode "^2.1.1"
     webidl-conversions "^5.0.0"
 
+whatwg-url@^11.0.0:
+  version "11.0.0"
+  resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-11.0.0.tgz#0a849eebb5faf2119b901bb76fd795c2848d4018"
+  integrity sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==
+  dependencies:
+    tr46 "^3.0.0"
+    webidl-conversions "^7.0.0"
+
 whatwg-url@^5.0.0:
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
@@ -14086,6 +16431,16 @@ wildcard@^2.0.0:
   resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec"
   integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==
 
+wonka@^4.0.14:
+  version "4.0.15"
+  resolved "https://registry.yarnpkg.com/wonka/-/wonka-4.0.15.tgz#9aa42046efa424565ab8f8f451fcca955bf80b89"
+  integrity sha512-U0IUQHKXXn6PFo9nqsHphVCE5m3IntqZNB9Jjn7EB1lrR7YTDY3YWgFvEvwniTzXSvOH/XMzAZaIfJF/LvHYXg==
+
+wonka@^6.1.2:
+  version "6.2.3"
+  resolved "https://registry.yarnpkg.com/wonka/-/wonka-6.2.3.tgz#88f7852a23a3d53bca7411c70d66e9ce8f93a366"
+  integrity sha512-EFOYiqDeYLXSzGYt2X3aVe9Hq1XJG+Hz/HjTRRT4dZE9q95khHl5+7pzUSXI19dbMO1/2UMrTf7JT7/7JrSQSQ==
+
 word-wrap@^1.2.3, word-wrap@~1.2.3:
   version "1.2.3"
   resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
@@ -14327,21 +16682,64 @@ ws@^7, ws@^7.0.0, ws@^7.4.6, ws@^7.5.1:
   resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591"
   integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==
 
-ws@^8.4.2:
+ws@^8.11.0, ws@^8.4.2:
   version "8.12.1"
   resolved "https://registry.yarnpkg.com/ws/-/ws-8.12.1.tgz#c51e583d79140b5e42e39be48c934131942d4a8f"
   integrity sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew==
 
+xcode@^3.0.0, xcode@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/xcode/-/xcode-3.0.1.tgz#3efb62aac641ab2c702458f9a0302696146aa53c"
+  integrity sha512-kCz5k7J7XbJtjABOvkc5lJmkiDh8VhjVCGNiqdKCscmVpdVUpEAyXv1xmCLkQJ5dsHqx3IPO4XW+NTDhU/fatA==
+  dependencies:
+    simple-plist "^1.1.0"
+    uuid "^7.0.3"
+
+xml-js@^1.6.11:
+  version "1.6.11"
+  resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.6.11.tgz#927d2f6947f7f1c19a316dd8eea3614e8b18f8e9"
+  integrity sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==
+  dependencies:
+    sax "^1.2.4"
+
 xml-name-validator@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
   integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==
 
+xml-name-validator@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835"
+  integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==
+
+xml2js@0.4.23:
+  version "0.4.23"
+  resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66"
+  integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==
+  dependencies:
+    sax ">=0.6.0"
+    xmlbuilder "~11.0.0"
+
 xml@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5"
   integrity sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==
 
+xmlbuilder@^14.0.0:
+  version "14.0.0"
+  resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-14.0.0.tgz#876b5aec4f05ffd5feb97b0a871c855d16fbeb8c"
+  integrity sha512-ts+B2rSe4fIckR6iquDjsKbQFK2NlUk6iG5nf14mDEyldgoc2nEKZ3jZWMPTxGQwVgToSjt6VGIho1H8/fNFTg==
+
+xmlbuilder@^15.1.1:
+  version "15.1.1"
+  resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5"
+  integrity sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==
+
+xmlbuilder@~11.0.0:
+  version "11.0.1"
+  resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3"
+  integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==
+
 xmlchars@^2.2.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
@@ -14459,6 +16857,6 @@ yocto-queue@^0.1.0:
   integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
 
 zod@^3.14.2, zod@^3.20.2:
-  version "3.20.6"
-  resolved "https://registry.yarnpkg.com/zod/-/zod-3.20.6.tgz#2f2f08ff81291d47d99e86140fedb4e0db08361a"
-  integrity sha512-oyu0m54SGCtzh6EClBVqDDlAYRz4jrVtKwQ7ZnsEmMI9HnzuZFj8QFwAY1M5uniIYACdGvv0PBWPF2kO0aNofA==
+  version "3.21.4"
+  resolved "https://registry.yarnpkg.com/zod/-/zod-3.21.4.tgz#10882231d992519f0a10b5dd58a38c9dabbb64db"
+  integrity sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==