From 693488d2c0cb5493413bdf6e2ab750b785f575ac Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Mon, 18 Mar 2024 17:06:33 -0500 Subject: [PATCH] Revert "Merge branch 'develop' into staging" This reverts commit 1629c32ee5e14861a395ec45f6e9e2e97a0a5c09, reversing changes made to adaa30794aed105bcc514e60b3277267cfa8d2e9. --- .github/workflows/mobile-build.yml | 11 +- .github/workflows/mobile-update.yml | 12 +- apps/tlon-mobile/.env.sample | 4 - apps/tlon-mobile/README.md | 31 - apps/tlon-mobile/android/app/build.gradle | 2 +- .../android/app/google-services.json | 28 +- apps/tlon-mobile/app.config.ts | 10 +- apps/tlon-mobile/index.js | 6 - .../ios/Landscape.xcodeproj/project.pbxproj | 112 +- apps/tlon-mobile/ios/Podfile.lock | 83 - apps/tlon-mobile/package.json | 10 +- apps/tlon-mobile/src/App.tsx | 82 +- .../src/components/AuthenticatedApp.tsx | 49 - .../src/components/SingletonWebview.tsx | 184 +- .../src/components/WebviewOverlay.tsx | 23 - apps/tlon-mobile/src/constants.ts | 8 +- apps/tlon-mobile/src/contexts/branch.tsx | 155 -- .../src/contexts/webview/webview.tsx | 60 +- apps/tlon-mobile/src/db/hooks.ts | 24 - apps/tlon-mobile/src/db/index.ts | 1 - apps/tlon-mobile/src/db/queries.ts | 87 - apps/tlon-mobile/src/db/realm.tsx | 49 +- apps/tlon-mobile/src/db/schemas.ts | 42 +- apps/tlon-mobile/src/hooks/useAppStatus.ts | 19 - apps/tlon-mobile/src/hooks/useDeepLink.ts | 110 + .../src/hooks/useDeepLinkListener.ts | 65 - apps/tlon-mobile/src/hooks/useLogout.ts | 17 - .../tlon-mobile/src/hooks/useManageAccount.ts | 72 - .../src/hooks/useNotificationListener.ts | 70 - apps/tlon-mobile/src/lib/api.ts | 78 - apps/tlon-mobile/src/lib/contactsApi.ts | 8 +- apps/tlon-mobile/src/lib/hostingApi.ts | 10 +- apps/tlon-mobile/src/lib/subscribe.ts | 14 - apps/tlon-mobile/src/lib/sync.ts | 14 - apps/tlon-mobile/src/lib/unreadsApi.test.ts | 60 - apps/tlon-mobile/src/lib/unreadsApi.ts | 66 - apps/tlon-mobile/src/lib/useDevTools.ts | 44 + apps/tlon-mobile/src/navigation/TabStack.tsx | 191 +- .../src/navigation/WebViewStack.tsx | 19 +- .../src/screens/ExternalWebViewScreen.tsx | 10 - .../src/screens/ReserveShipScreen.tsx | 4 +- .../src/screens/ShipLoginScreen.tsx | 13 +- .../src/screens/TlonLoginScreen.tsx | 11 +- .../src/screens/WebviewPlaceholderScreen.tsx | 6 +- apps/tlon-mobile/src/types.ts | 27 +- apps/tlon-web/e2e/shipManifest.json | 8 +- apps/tlon-web/package.json | 21 +- apps/tlon-web/playwright.config.ts | 4 +- apps/tlon-web/rube/index.ts | 15 +- apps/tlon-web/src/api.ts | 3 +- apps/tlon-web/src/chat/ChatChannel.tsx | 6 +- .../tlon-web/src/chat/ChatInput/ChatInput.tsx | 58 +- .../src/chat/ChatMessage/ChatMessage.tsx | 316 +-- .../chat/ChatMessage/ChatMessageOptions.tsx | 32 +- .../src/chat/ChatScroller/ChatScroller.tsx | 4 +- .../src/chat/ChatThread/ChatThread.tsx | 1 - apps/tlon-web/src/chat/ChatWindow.tsx | 2 +- .../tlon-web/src/components/MessageEditor.tsx | 9 +- apps/tlon-web/src/diary/DiaryChannel.tsx | 4 +- .../src/diary/DiaryList/DiaryGridView.tsx | 6 +- apps/tlon-web/src/groups/GroupJoinList.tsx | 1 - apps/tlon-web/src/heap/HeapChannel.tsx | 4 +- apps/tlon-web/src/logic/native.ts | 3 - apps/tlon-web/src/logic/scroll.ts | 16 +- .../src/logic/useIsEditingMessage.tsx | 9 - apps/tlon-web/src/logic/useShowTabBar.ts | 6 +- apps/tlon-web/src/logic/useSidebarSort.ts | 21 +- apps/tlon-web/src/manifest.ts | 10 +- apps/tlon-web/src/mocks/chat.ts | 2 +- apps/tlon-web/src/mocks/groups.ts | 6 +- apps/tlon-web/src/profiles/Profile.tsx | 8 +- apps/tlon-web/src/replies/ReplyMessage.tsx | 187 +- .../src/replies/ReplyMessageOptions.tsx | 27 - apps/tlon-web/src/state/channel/channel.ts | 240 +- apps/tlon-web/src/state/channel/keys.ts | 1 - apps/tlon-web/src/state/chat/chat.ts | 19 +- apps/tlon-web/tsconfig.json | 6 +- .../{vite.config.mts => vite.config.ts} | 56 +- desk/app/channels.hoon | 246 +- desk/app/chat.hoon | 33 +- desk/app/notify.hoon | 122 +- desk/app/profile.hoon | 11 - desk/app/profile/widgets.hoon | 2 +- desk/lib/channel-json.hoon | 106 +- desk/lib/channel-utils.hoon | 94 +- desk/lib/mark-warmer.hoon | 4 - desk/lib/test-agent.hoon | 110 +- desk/mar/channel/response-1.hoon | 15 - desk/mar/channel/response.hoon | 8 +- desk/mar/channel/simple-post.hoon | 14 - desk/mar/channel/simple-posts.hoon | 14 - desk/mar/channel/simple-replies.hoon | 14 - desk/mar/channel/simple-reply.hoon | 14 - desk/mar/channel/simple-response.hoon | 15 - desk/sur/channels.hoon | 44 +- desk/sur/notify.hoon | 10 +- desk/tests/app/chat.hoon | 119 +- desk/tests/lib/negotiate.hoon | 9 +- package-lock.json | 2019 +++++++++-------- package.json | 2 +- packages/shared/package.json | 3 +- packages/shared/src/client/index.ts | 19 - packages/shared/src/index.ts | 1 - packages/shared/src/types/native.ts | 1 - packages/shared/src/urbit/channel.ts | 86 +- packages/shared/src/urbit/dms.ts | 28 +- packages/ui/src/components/Avatar.tsx | 31 - packages/ui/src/components/UrbitSigil.tsx | 3 +- packages/ui/src/index.ts | 3 +- patches/@tiptap+react+2.0.3.patch | 13 - ...ev-2.patch => @urbit+http-api+3.0.0.patch} | 14 +- patches/vite-plugin-pwa+0.14.4.patch | 34 + 112 files changed, 2176 insertions(+), 4197 deletions(-) delete mode 100644 apps/tlon-mobile/src/components/AuthenticatedApp.tsx delete mode 100644 apps/tlon-mobile/src/components/WebviewOverlay.tsx delete mode 100644 apps/tlon-mobile/src/contexts/branch.tsx delete mode 100644 apps/tlon-mobile/src/db/hooks.ts delete mode 100644 apps/tlon-mobile/src/db/queries.ts delete mode 100644 apps/tlon-mobile/src/hooks/useAppStatus.ts create mode 100644 apps/tlon-mobile/src/hooks/useDeepLink.ts delete mode 100644 apps/tlon-mobile/src/hooks/useDeepLinkListener.ts delete mode 100644 apps/tlon-mobile/src/hooks/useLogout.ts delete mode 100644 apps/tlon-mobile/src/hooks/useManageAccount.ts delete mode 100644 apps/tlon-mobile/src/hooks/useNotificationListener.ts delete mode 100644 apps/tlon-mobile/src/lib/subscribe.ts delete mode 100644 apps/tlon-mobile/src/lib/unreadsApi.test.ts delete mode 100644 apps/tlon-mobile/src/lib/unreadsApi.ts create mode 100644 apps/tlon-mobile/src/lib/useDevTools.ts delete mode 100644 apps/tlon-web/src/logic/useIsEditingMessage.tsx rename apps/tlon-web/{vite.config.mts => vite.config.ts} (78%) delete mode 100644 desk/mar/channel/response-1.hoon delete mode 100644 desk/mar/channel/simple-post.hoon delete mode 100644 desk/mar/channel/simple-posts.hoon delete mode 100644 desk/mar/channel/simple-replies.hoon delete mode 100644 desk/mar/channel/simple-reply.hoon delete mode 100644 desk/mar/channel/simple-response.hoon delete mode 100644 packages/shared/src/client/index.ts delete mode 100644 packages/ui/src/components/Avatar.tsx delete mode 100644 patches/@tiptap+react+2.0.3.patch rename patches/{@urbit+http-api+3.1.0-dev-2.patch => @urbit+http-api+3.0.0.patch} (65%) create mode 100644 patches/vite-plugin-pwa+0.14.4.patch diff --git a/.github/workflows/mobile-build.yml b/.github/workflows/mobile-build.yml index 8490333a7b..026a436b79 100644 --- a/.github/workflows/mobile-build.yml +++ b/.github/workflows/mobile-build.yml @@ -15,6 +15,9 @@ on: - all - android - ios +env: + NOTIFY_PROVIDER: rivfur-livmet + NOTIFY_SERVICE: groups-native jobs: deploy: runs-on: ubuntu-latest @@ -33,15 +36,9 @@ jobs: token: ${{ secrets.EXPO_TOKEN }} - name: Install dependencies run: npm ci - - name: Create build vars - id: vars - run: | - echo "profile=${{ inputs.profile || 'preview' }}" >> $GITHUB_OUTPUT - name: Build for selected platforms working-directory: ./apps/tlon-mobile - run: eas build --profile ${{ steps.vars.outputs.profile }} --platform ${{ inputs.platform || 'all' }} --non-interactive --auto-submit + run: eas build --profile ${{ inputs.profile || 'preview' }} --platform ${{ inputs.platform || 'all' }} --non-interactive --auto-submit env: EXPO_APPLE_ID: ${{ secrets.EXPO_APPLE_ID }} EXPO_APPLE_PASSWORD: ${{ secrets.EXPO_APPLE_PASSWORD }} - NOTIFY_PROVIDER: "${{ steps.vars.outputs.profile == 'preview' && 'binnec-dozzod-marnus' || 'rivfur-livmet' }}" - NOTIFY_SERVICE: "${{ steps.vars.outputs.profile == 'preview' && 'tlon-preview-release' || 'groups-native' }}" diff --git a/.github/workflows/mobile-update.yml b/.github/workflows/mobile-update.yml index f68441c4d9..0087665408 100644 --- a/.github/workflows/mobile-update.yml +++ b/.github/workflows/mobile-update.yml @@ -15,6 +15,9 @@ on: - all - android - ios +env: + NOTIFY_PROVIDER: rivfur-livmet + NOTIFY_SERVICE: groups-native jobs: deploy: runs-on: ubuntu-latest @@ -33,13 +36,6 @@ jobs: token: ${{ secrets.EXPO_TOKEN }} - name: Install dependencies run: npm ci - - name: Create build vars - id: vars - run: | - echo "profile=${{ inputs.profile || 'preview' }}" >> $GITHUB_OUTPUT - name: Push update for selected platforms working-directory: ./apps/tlon-mobile - run: eas update --auto --profile ${{ steps.vars.outputs.profile }} --platform ${{ inputs.platform || 'all' }} --non-interactive - env: - NOTIFY_PROVIDER: "${{ steps.vars.outputs.profile == 'preview' && 'binnec-dozzod-marnus' || 'rivfur-livmet' }}" - NOTIFY_SERVICE: "${{ steps.vars.outputs.profile == 'preview' && 'tlon-preview-release' || 'groups-native' }}" + run: eas update --auto --profile ${{ inputs.profile || 'preview' }} --platform ${{ inputs.platform || 'all' }} --non-interactive diff --git a/apps/tlon-mobile/.env.sample b/apps/tlon-mobile/.env.sample index bf26e98a05..e21f49f9a1 100644 --- a/apps/tlon-mobile/.env.sample +++ b/apps/tlon-mobile/.env.sample @@ -9,7 +9,3 @@ DEFAULT_LURE= DEFAULT_PRIORITY_TOKEN= RECAPTCHA_SITE_KEY_ANDROID= RECAPTCHA_SITE_KEY_IOS= -DEFAULT_TLON_LOGIN_EMAIL= -DEFAULT_TLON_LOGIN_PASSWORD= -DEFAULT_SHIP_LOGIN_URL= -DEFAULT_SHIP_LOGIN_ACCESS_CODE= diff --git a/apps/tlon-mobile/README.md b/apps/tlon-mobile/README.md index 8eb9affebd..ca12dd70ba 100644 --- a/apps/tlon-mobile/README.md +++ b/apps/tlon-mobile/README.md @@ -70,37 +70,6 @@ Plug in your iOS device or start the iPhone Simulator and run the application in npm run ios ``` -#### Run Preview Scheme - -Run the Preview version of the app by specifying `scheme` or `variant` in the run command: - -```sh -npm run ios -- --scheme=Landscape-preview -``` - -```sh -npm run android -- --variant=preview -``` - -## Debugging - -### Dev tools - -Press `j` while running expo-cli/metro to open chrome devtools. You can use the devtools to view logs, network requests, and more. [More info here](https://docs.expo.dev/debugging/tools/#debugging-with-chrome-devtools). - -### Default Credentials - -To streamline testing the login flow, you can use env variables to prepopulate fields in the Tlon Login and ship login screen. The relevant variables are: - -``` -DEFAULT_TLON_LOGIN_EMAIL= -DEFAULT_TLON_LOGIN_PASSWORD= -DEFAULT_SHIP_LOGIN_URL= -DEFAULT_SHIP_LOGIN_ACCESS_CODE= -``` - -See `.env.sample` for other configurable env variables. - ## Deployment Deployment is handled by [Expo Application Services](https://expo.dev/eas). diff --git a/apps/tlon-mobile/android/app/build.gradle b/apps/tlon-mobile/android/app/build.gradle index 1fcf362872..87d8824dc8 100644 --- a/apps/tlon-mobile/android/app/build.gradle +++ b/apps/tlon-mobile/android/app/build.gradle @@ -87,7 +87,7 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion compileSdk rootProject.ext.compileSdkVersion - versionCode 62 + versionCode 48 versionName "4.0.0" buildConfigField("boolean", "REACT_NATIVE_UNSTABLE_USE_RUNTIME_SCHEDULER_ALWAYS", (findProperty("reactNative.unstable_useRuntimeSchedulerAlways") ?: true).toString()) diff --git a/apps/tlon-mobile/android/app/google-services.json b/apps/tlon-mobile/android/app/google-services.json index f99c9075f4..7f3b21a7f8 100644 --- a/apps/tlon-mobile/android/app/google-services.json +++ b/apps/tlon-mobile/android/app/google-services.json @@ -12,7 +12,12 @@ "package_name": "io.tlon.groups" } }, - "oauth_client": [], + "oauth_client": [ + { + "client_id": "543296749236-1l5r4ipguvjdh083t5j7h1eicqi21j0o.apps.googleusercontent.com", + "client_type": 3 + } + ], "api_key": [ { "current_key": "AIzaSyDm8-5daeW5B8j6i_RIoSvOgnx6Fo0gEOU" @@ -20,7 +25,12 @@ ], "services": { "appinvite_service": { - "other_platform_oauth_client": [] + "other_platform_oauth_client": [ + { + "client_id": "543296749236-1l5r4ipguvjdh083t5j7h1eicqi21j0o.apps.googleusercontent.com", + "client_type": 3 + } + ] } } }, @@ -31,7 +41,12 @@ "package_name": "io.tlon.groups.preview" } }, - "oauth_client": [], + "oauth_client": [ + { + "client_id": "543296749236-1l5r4ipguvjdh083t5j7h1eicqi21j0o.apps.googleusercontent.com", + "client_type": 3 + } + ], "api_key": [ { "current_key": "AIzaSyDm8-5daeW5B8j6i_RIoSvOgnx6Fo0gEOU" @@ -39,7 +54,12 @@ ], "services": { "appinvite_service": { - "other_platform_oauth_client": [] + "other_platform_oauth_client": [ + { + "client_id": "543296749236-1l5r4ipguvjdh083t5j7h1eicqi21j0o.apps.googleusercontent.com", + "client_type": 3 + } + ] } } } diff --git a/apps/tlon-mobile/app.config.ts b/apps/tlon-mobile/app.config.ts index 5ea61d26e3..efb6cb195f 100644 --- a/apps/tlon-mobile/app.config.ts +++ b/apps/tlon-mobile/app.config.ts @@ -23,23 +23,21 @@ export default ({ config }: ConfigContext): ExpoConfig => ({ shipUrlPattern: process.env.SHIP_URL_PATTERN, defaultLure: process.env.DEFAULT_LURE, defaultPriorityToken: process.env.DEFAULT_PRIORITY_TOKEN, - defaultTlonLoginEmail: process.env.DEFAULT_TLON_LOGIN_EMAIL, - defaultTlonLoginPassword: process.env.DEFAULT_TLON_LOGIN_PASSWORD, - defaultShipLoginUrl: process.env.DEFAULT_SHIP_LOGIN_URL, - defaultShipLoginAccessCode: process.env.DEFAULT_SHIP_LOGIN_ACCESS_CODE, recaptchaSiteKeyAndroid: process.env.RECAPTCHA_SITE_KEY_ANDROID, recaptchaSiteKeyIOS: process.env.RECAPTCHA_SITE_KEY_IOS, + devLocal: Boolean(process.env.DEV_LOCAL), + devLocalCode: process.env.DEV_LOCAL_CODE, }, ios: { runtimeVersion: '4.0.0', - buildNumber: '62', + buildNumber: '48', config: { usesNonExemptEncryption: false, }, }, android: { runtimeVersion: '4.0.0', - versionCode: 62, + versionCode: 48, }, updates: { url: `https://u.expo.dev/${projectId}`, diff --git a/apps/tlon-mobile/index.js b/apps/tlon-mobile/index.js index aabe367562..d0b1debe2a 100644 --- a/apps/tlon-mobile/index.js +++ b/apps/tlon-mobile/index.js @@ -1,15 +1,9 @@ import { registerRootComponent } from 'expo'; -import 'expo-dev-client'; -import polyfill from 'react-native-polyfill-globals'; import { TailwindProvider } from 'tailwind-rn'; import App from './src/App'; import utilities from './tailwind.json'; -// Modifies fetch to support server sent events which -// are required for Urbit client subscriptions -polyfill(); - function Main(props) { return ( diff --git a/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj b/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj index c3612e9ae6..a5e0dbc119 100644 --- a/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj +++ b/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj @@ -653,16 +653,12 @@ "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/EXUpdates/EXUpdates.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.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}/EXUpdates.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXDevLauncher.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXDevMenu.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -733,16 +729,12 @@ "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/EXUpdates/EXUpdates.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.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}/EXUpdates.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXDevLauncher.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXDevMenu.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -917,7 +909,7 @@ CLANG_DEBUG_INFORMATION_LEVEL = "DWARF with dSYM File"; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Landscape/Landscape.entitlements; - CURRENT_PROJECT_VERSION = 62; + CURRENT_PROJECT_VERSION = 48; DEVELOPMENT_TEAM = XU9PR2N722; DISPLAY_NAME = Tlon; ENABLE_BITCODE = NO; @@ -955,7 +947,7 @@ CLANG_DEBUG_INFORMATION_LEVEL = "DWARF with dSYM File"; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Landscape/Landscape.entitlements; - CURRENT_PROJECT_VERSION = 62; + CURRENT_PROJECT_VERSION = 48; DEVELOPMENT_TEAM = XU9PR2N722; DISPLAY_NAME = Tlon; INFOPLIST_FILE = Landscape/Info.plist; @@ -987,7 +979,7 @@ CLANG_DEBUG_INFORMATION_LEVEL = "DWARF with dSYM File"; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Landscape/Landscape.entitlements; - CURRENT_PROJECT_VERSION = 62; + CURRENT_PROJECT_VERSION = 48; DEVELOPMENT_TEAM = XU9PR2N722; DISPLAY_NAME = "Tlon - Preview"; ENABLE_BITCODE = NO; @@ -1026,7 +1018,7 @@ CLANG_DEBUG_INFORMATION_LEVEL = "DWARF with dSYM File"; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Landscape/Landscape.entitlements; - CURRENT_PROJECT_VERSION = 62; + CURRENT_PROJECT_VERSION = 48; DEVELOPMENT_TEAM = XU9PR2N722; DISPLAY_NAME = "Tlon - Preview"; INFOPLIST_FILE = Landscape/Info.plist; @@ -1182,30 +1174,30 @@ "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers/platform/ios", "${PODS_CONFIGURATION_BUILD_DIR}/React-NativeModulesApple/React_NativeModulesApple.framework/Headers", "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios", - "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", - "${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", - "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", - "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", - "${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", - "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", - "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", - "${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", - "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", - "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", - "${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", - "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", - "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", - "${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", - "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", - "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", - "${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", - "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", - "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", - "${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", - "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", - "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", - "${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", - "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", ); IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -1276,30 +1268,30 @@ "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers/platform/ios", "${PODS_CONFIGURATION_BUILD_DIR}/React-NativeModulesApple/React_NativeModulesApple.framework/Headers", "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios", - "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", - "${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", - "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", - "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", - "${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", - "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", - "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", - "${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", - "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", - "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", - "${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", - "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", - "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", - "${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", - "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", - "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", - "${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", - "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", - "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", - "${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", - "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", - "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", - "${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", - "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", ); IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/apps/tlon-mobile/ios/Podfile.lock b/apps/tlon-mobile/ios/Podfile.lock index 01efc90c86..7aaa2afc03 100644 --- a/apps/tlon-mobile/ios/Podfile.lock +++ b/apps/tlon-mobile/ios/Podfile.lock @@ -17,73 +17,6 @@ PODS: - ExpoModulesCore - Expo (50.0.6): - ExpoModulesCore - - expo-dev-client (3.3.9): - - EXManifests - - expo-dev-launcher - - expo-dev-menu - - expo-dev-menu-interface - - EXUpdatesInterface - - expo-dev-launcher (3.6.7): - - EXManifests - - expo-dev-launcher/Main (= 3.6.7) - - expo-dev-menu - - expo-dev-menu-interface - - ExpoModulesCore - - EXUpdatesInterface - - glog - - RCT-Folly (= 2022.05.16.00) - - React-Core - - React-RCTAppDelegate - - expo-dev-launcher/Main (3.6.7): - - EXManifests - - expo-dev-launcher/Unsafe - - expo-dev-menu - - expo-dev-menu-interface - - ExpoModulesCore - - EXUpdatesInterface - - glog - - RCT-Folly (= 2022.05.16.00) - - React-Core - - React-RCTAppDelegate - - expo-dev-launcher/Unsafe (3.6.7): - - EXManifests - - expo-dev-menu - - expo-dev-menu-interface - - ExpoModulesCore - - EXUpdatesInterface - - glog - - RCT-Folly (= 2022.05.16.00) - - React-Core - - React-RCTAppDelegate - - expo-dev-menu (4.5.6): - - expo-dev-menu/Main (= 4.5.6) - - expo-dev-menu/ReactNativeCompatibles (= 4.5.6) - - glog - - RCT-Folly (= 2022.05.16.00) - - React-Core - - expo-dev-menu-interface (1.7.2) - - expo-dev-menu/Main (4.5.6): - - EXManifests - - expo-dev-menu-interface - - expo-dev-menu/Vendored - - ExpoModulesCore - - glog - - RCT-Folly (= 2022.05.16.00) - - React-Core - - expo-dev-menu/ReactNativeCompatibles (4.5.6): - - glog - - RCT-Folly (= 2022.05.16.00) - - React-Core - - expo-dev-menu/SafeAreaView (4.5.6): - - ExpoModulesCore - - glog - - RCT-Folly (= 2022.05.16.00) - - React-Core - - expo-dev-menu/Vendored (4.5.6): - - expo-dev-menu/SafeAreaView - - glog - - RCT-Folly (= 2022.05.16.00) - - React-Core - ExpoClipboard (5.0.1): - ExpoModulesCore - ExpoDevice (5.9.3): @@ -1294,10 +1227,6 @@ DEPENDENCIES: - EXManifests (from `../../../node_modules/expo-manifests/ios`) - EXNotifications (from `../../../node_modules/expo-notifications/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`) - ExpoClipboard (from `../../../node_modules/expo-clipboard/ios`) - ExpoDevice (from `../../../node_modules/expo-device/ios`) - ExpoFileSystem (from `../../../node_modules/expo-file-system/ios`) @@ -1419,14 +1348,6 @@ EXTERNAL SOURCES: :path: "../../../node_modules/expo-notifications/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" ExpoClipboard: :path: "../../../node_modules/expo-clipboard/ios" ExpoDevice: @@ -1587,10 +1508,6 @@ SPEC CHECKSUMS: EXManifests: 5e8c29f36c716af768a4ea47ec05e1b89ab93091 EXNotifications: e11f0e9a5b657c064a481a5d522f3bc5a07bf7cd Expo: fb745b3074989670b6641f9f20463e8ee56a69ca - expo-dev-client: dbc8e8a81d17a9d92e083a2856d056ba9a58984d - expo-dev-launcher: fcdb0f3e91c34778f0816b0f590c7a57f052a87c - expo-dev-menu: 166fc9c7b82641cdead1dc26d958d20a127ee97b - expo-dev-menu-interface: 7ba029c9d1a82ac22b9b584c00514860b060553e ExpoClipboard: b597982124f067ff9f5b89093eb3d97898d5d877 ExpoDevice: d204395e17fffdcefa7470bdef33b07719ac41b1 ExpoFileSystem: a9273932e69a9a1e1a8d01b6ba895bb8294bbea2 diff --git a/apps/tlon-mobile/package.json b/apps/tlon-mobile/package.json index 0186402682..3112f1ae71 100644 --- a/apps/tlon-mobile/package.json +++ b/apps/tlon-mobile/package.json @@ -41,7 +41,6 @@ "@realm/react": "^0.6.2", "@tloncorp/shared": "*", "@urbit/aura": "^1.0.0", - "@urbit/http-api": "^3.1.0-dev-2", "classnames": "^2.3.2", "dotenv-expand": "^11.0.6", "expo": "^50.0.6", @@ -49,7 +48,6 @@ "expo-asset": "~9.0.2", "expo-clipboard": "~5.0.1", "expo-constants": "~15.4.5", - "expo-dev-client": "^3.3.9", "expo-device": "~5.9.3", "expo-file-system": "~16.0.6", "expo-localization": "~14.8.3", @@ -67,23 +65,17 @@ "react-native-branch": "^5.9.0", "react-native-country-codes-picker": "^2.3.3", "react-native-device-info": "^10.8.0", - "react-native-fetch-api": "^3.0.0", "react-native-gesture-handler": "~2.14.0", - "react-native-get-random-values": "^1.11.0", "react-native-phone-input": "^1.3.7", - "react-native-polyfill-globals": "^3.1.0", "react-native-reanimated": "~3.6.2", "react-native-safe-area-context": "4.8.2", "react-native-screens": "~3.29.0", - "react-native-sse": "^1.2.1", "react-native-storage": "^1.0.1", "react-native-svg": "^14.1.0", "react-native-url-polyfill": "^2.0.0", "react-native-webview": "13.6.4", "realm": "^12.6.0", - "tailwind-rn": "^4.2.0", - "text-encoding": "^0.7.0", - "web-streams-polyfill": "^3.3.3" + "tailwind-rn": "^4.2.0" }, "devDependencies": { "@react-native/metro-config": "^0.73.5", diff --git a/apps/tlon-mobile/src/App.tsx b/apps/tlon-mobile/src/App.tsx index 98a99b48f8..49507f8823 100644 --- a/apps/tlon-mobile/src/App.tsx +++ b/apps/tlon-mobile/src/App.tsx @@ -1,25 +1,31 @@ import NetInfo from '@react-native-community/netinfo'; import { + CommonActions, DarkTheme, DefaultTheme, NavigationContainer, + useNavigation, } from '@react-navigation/native'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; import { TamaguiProvider } from '@tloncorp/ui'; import { PostHogProvider } from 'posthog-react-native'; import { useEffect, useState } from 'react'; -import { StatusBar, Text, View } from 'react-native'; +import { Alert, StatusBar, Text, View } from 'react-native'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { useTailwind } from 'tailwind-rn'; -import AuthenticatedApp from './components/AuthenticatedApp'; import { LoadingSpinner } from './components/LoadingSpinner'; -import { BranchProvider, useBranch } from './contexts/branch'; +import { DEV_LOCAL, DEV_LOCAL_CODE } from './constants'; import { ShipProvider, useShip } from './contexts/ship'; import * as db from './db'; +import { useDeepLink } from './hooks/useDeepLink'; import { useIsDarkMode } from './hooks/useIsDarkMode'; import { useScreenOptions } from './hooks/useScreenOptions'; +import { inviteShipWithLure } from './lib/hostingApi'; +import { syncContacts } from './lib/sync'; +import { useDevTools } from './lib/useDevTools'; +import { TabStack } from './navigation/TabStack'; import { CheckVerifyScreen } from './screens/CheckVerifyScreen'; import { EULAScreen } from './screens/EULAScreen'; import { JoinWaitListScreen } from './screens/JoinWaitListScreen'; @@ -35,7 +41,7 @@ import { SignUpPasswordScreen } from './screens/SignUpPasswordScreen'; import { TlonLoginScreen } from './screens/TlonLoginScreen'; import { WelcomeScreen } from './screens/WelcomeScreen'; import type { OnboardingStackParamList } from './types'; -import { posthogAsync } from './utils/posthog'; +import { posthogAsync, trackError } from './utils/posthog'; import { getPathFromWer } from './utils/string'; type Props = { @@ -44,15 +50,22 @@ type Props = { const OnboardingStack = createNativeStackNavigator(); -// on Android if a notification click causes the app to open, the corresponding notification -// path is passed in here as "wer" -const App = ({ wer }: Props) => { +const App = ({ wer: initialWer }: Props) => { + useDevTools({ enabled: DEV_LOCAL, localCode: DEV_LOCAL_CODE }); const isDarkMode = useIsDarkMode(); const tailwind = useTailwind(); - const { isLoading, isAuthenticated } = useShip(); + const { isLoading, isAuthenticated, ship } = useShip(); const [connected, setConnected] = useState(true); - const { lure, priorityToken } = useBranch(); + const { wer, lure, priorityToken, clearDeepLink } = useDeepLink(); + const navigation = useNavigation(); const screenOptions = useScreenOptions(); + const gotoPath = initialWer ? getPathFromWer(initialWer) : wer; + + useEffect(() => { + if (isAuthenticated) { + syncContacts(); + } + }, [isAuthenticated]); useEffect(() => { const unsubscribeFromNetInfo = NetInfo.addEventListener( @@ -66,6 +79,45 @@ const App = ({ wer }: Props) => { }; }, []); + useEffect(() => { + // User received a lure link while authenticated + if (ship && lure) { + (async () => { + try { + await inviteShipWithLure({ ship, lure }); + Alert.alert( + '', + 'Your invitation to the group is on its way. It will appear in the Groups list.', + [ + { + text: 'OK', + onPress: () => null, + }, + ], + { cancelable: true } + ); + } catch (err) { + console.error('Error inviting ship with lure:', err); + if (err instanceof Error) { + trackError(err); + } + } + + clearDeepLink(); + })(); + } + }, [ship, lure, clearDeepLink]); + + useEffect(() => { + // Broadcast path update to webview when changed + if (isAuthenticated && gotoPath) { + navigation.dispatch(CommonActions.setParams({ gotoPath })); + + // Clear the deep link to mark it as handled + clearDeepLink(); + } + }, [isAuthenticated, gotoPath, navigation, clearDeepLink]); + return ( @@ -76,9 +128,7 @@ const App = ({ wer }: Props) => { ) : isAuthenticated ? ( - + ) : ( - - - - - + + + diff --git a/apps/tlon-mobile/src/components/AuthenticatedApp.tsx b/apps/tlon-mobile/src/components/AuthenticatedApp.tsx deleted file mode 100644 index 2b3578f2cd..0000000000 --- a/apps/tlon-mobile/src/components/AuthenticatedApp.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { ZStack } from '@tloncorp/ui'; -import { useEffect } from 'react'; - -import { useShip } from '../contexts/ship'; -import { WebviewPositionProvider } from '../contexts/webview/position'; -import { WebviewProvider } from '../contexts/webview/webview'; -import { useDeepLinkListener } from '../hooks/useDeepLinkListener'; -import useNotificationListener from '../hooks/useNotificationListener'; -import { initializeUrbitClient } from '../lib/api'; -import { subscribeUnreads } from '../lib/subscribe'; -import { syncContacts, syncUnreads } from '../lib/sync'; -import { TabStack } from '../navigation/TabStack'; -import WebviewOverlay from './WebviewOverlay'; - -export interface AuthenticatedAppProps { - initialNotificationPath?: string; -} - -function AuthenticatedApp({ initialNotificationPath }: AuthenticatedAppProps) { - const { ship, shipUrl } = useShip(); - useNotificationListener(initialNotificationPath); - useDeepLinkListener(); - - useEffect(() => { - initializeUrbitClient(ship ?? '', shipUrl ?? ''); - syncContacts(); - syncUnreads(); - subscribeUnreads(); - }, [ship, shipUrl]); - - return ( - - - - - ); -} - -export default function ConnectedAuthenticatedApp( - props: AuthenticatedAppProps -) { - return ( - - - - - - ); -} diff --git a/apps/tlon-mobile/src/components/SingletonWebview.tsx b/apps/tlon-mobile/src/components/SingletonWebview.tsx index f56a894d9e..a4949ea038 100644 --- a/apps/tlon-mobile/src/components/SingletonWebview.tsx +++ b/apps/tlon-mobile/src/components/SingletonWebview.tsx @@ -1,30 +1,32 @@ -import crashlytics from '@react-native-firebase/crashlytics'; +import { useFocusEffect } from '@react-navigation/native'; +// import type { NativeStackScreenProps } from '@react-navigation/native-stack'; import { type MobileNavTab, type NativeWebViewOptions, type WebAppAction, - trimFullPath, } from '@tloncorp/shared'; import * as Clipboard from 'expo-clipboard'; -import { useEffect, useMemo, useRef, useState } from 'react'; +import { addNotificationResponseReceivedListener } from 'expo-notifications'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { Alert, Linking, useColorScheme } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { URL } from 'react-native-url-polyfill'; import { WebView } from 'react-native-webview'; -import type { - WebViewRenderProcessGoneEvent, - WebViewTerminatedEvent, -} from 'react-native-webview/lib/WebViewTypes'; import { useTailwind } from 'tailwind-rn'; -import { DEFAULT_SHIP_LOGIN_URL, DEFAULT_TLON_LOGIN_EMAIL } from '../constants'; +import { DEV_LOCAL } from '../constants'; import { useShip } from '../contexts/ship'; import { useWebViewContext } from '../contexts/webview/webview'; -import useAppStatus from '../hooks/useAppStatus'; -import { useLogout } from '../hooks/useLogout'; import { useWebView } from '../hooks/useWebView'; import WebAppHelpers from '../lib/WebAppHelpers'; -import { isUsingTlonAuth } from '../lib/hostingApi'; +import { markChatRead } from '../lib/chatApi'; +import { getHostingUser } from '../lib/hostingApi'; +// import { connectNotifications } from '../lib/notifications'; +import { + getHostingUserId, + removeHostingToken, + removeHostingUserId, +} from '../utils/hosting'; // TODO: add typing for data beyond generic value string type WebAppCommand = { @@ -32,19 +34,6 @@ type WebAppCommand = { value?: string | { tab: string; path: string }; }; -// used for tracking and recovering from crashes -interface CrashState { - isCrashed: boolean; - crashEvent: WebViewTerminatedEvent | WebViewRenderProcessGoneEvent | null; - lastPath: string; -} - -// used for forcing the webview to reload -interface SourceState { - url: string; - key: number; -} - const createUri = (shipUrl: string, path?: string) => `${shipUrl}/apps/groups${ path ? (path.startsWith('/') ? path : `/${path}`) : '/' @@ -56,36 +45,16 @@ export const SingletonWebview = () => { const webViewProps = useWebView(); const colorScheme = useColorScheme(); const safeAreaInsets = useSafeAreaInsets(); - const { handleLogout } = useLogout(); const webviewRef = useRef(null); + const didManageAccount = useRef(false); + const [appLoaded, setAppLoaded] = useState(false); const webviewContext = useWebViewContext(); - const initialUrl = useMemo(() => createUri(shipUrl, '/'), [shipUrl]); - const appStatus = useAppStatus(); - - const [crashRecovery, setCrashRecovery] = useState({ - isCrashed: false, - crashEvent: null, - lastPath: '', - }); - const [source, setSource] = useState({ - url: initialUrl, - key: 0, - }); - - // If the webview crashed, wait until it's back in the foreground to reload - useEffect(() => { - if (appStatus === 'active' && crashRecovery.isCrashed) { - setSource({ - url: createUri(shipUrl, crashRecovery.lastPath), - key: source.key + 1, - }); - setCrashRecovery((prev) => ({ - ...prev, - isCrashed: false, - })); - } - }, [appStatus, crashRecovery, shipUrl, source]); + const handleLogout = useCallback(() => { + clearShip(); + removeHostingToken(); + removeHostingUserId(); + }, [clearShip]); const handleMessage = async ({ action, value }: WebAppCommand) => { switch (action) { @@ -115,7 +84,7 @@ export const SingletonWebview = () => { ); break; case 'activeTabChange': - webviewContext.setNewWebappTab(value as MobileNavTab); + webviewContext.setGotoTab(value as MobileNavTab); break; case 'saveLastPath': { if (!value || typeof value !== 'object' || !value.tab || !value.path) { @@ -130,18 +99,81 @@ export const SingletonWebview = () => { } break; } + // TODO: handle manage account case 'manageAccount': - webviewContext.setManageAccountState('triggered'); + webviewContext.setDidManageAccount(true); break; case 'appLoaded': // Slight delay otherwise white background flashes setTimeout(() => { - webviewContext.setAppLoaded(true); + setAppLoaded(true); }, 100); break; } }; + useEffect(() => { + // Start notification tap listener + // This only seems to get triggered on iOS. Android handles the tap and other intents in native code. + const notificationTapListener = addNotificationResponseReceivedListener( + (response) => { + const { + actionIdentifier, + userText, + notification: { + request: { + content: { data }, + }, + }, + } = response; + if (actionIdentifier === 'markAsRead' && data.channelId) { + markChatRead(data.channelId); + } else if (actionIdentifier === 'reply' && userText) { + // Send reply + } else if (data.wer) { + // TODO: handle wer + // setUri((curr) => ({ + // ...curr, + // uri: createUri(shipUrl, data.wer), + // key: curr.key + 1, + // })); + } + } + ); + + // connectNotifications(); + + return () => { + // Clean up listeners + notificationTapListener.remove(); + }; + }, [shipUrl]); + + // When this view regains focus from Manage Account, query for hosting user's details and bump back to login if an error occurs + useFocusEffect( + useCallback(() => { + if (!didManageAccount.current) { + return; + } + + (async () => { + const hostingUserId = await getHostingUserId(); + if (hostingUserId) { + try { + const user = await getHostingUser(hostingUserId); + if (user.verified) { + didManageAccount.current = false; + } else { + handleLogout(); + } + } catch (err) { + handleLogout(); + } + } + })(); + }, [handleLogout]) + ); + useEffect(() => { // Path was changed by the parent view if (webviewContext.gotoPath) { @@ -160,7 +192,6 @@ export const SingletonWebview = () => { const nativeOptions: NativeWebViewOptions = { colorScheme, hideTabBar: true, - isUsingTlonAuth: isUsingTlonAuth(), safeAreaInsets, }; @@ -169,10 +200,9 @@ export const SingletonWebview = () => { {...webViewProps} scrollEnabled={false} ref={webviewRef} - key={source.key} - source={{ uri: source.url }} + source={{ uri: createUri(shipUrl, '/') }} style={ - webviewContext.appLoaded + appLoaded ? tailwind('bg-transparent') : { flex: 0, height: 0, opacity: 0 } } @@ -182,15 +212,14 @@ export const SingletonWebview = () => { window.colorscheme="${nativeOptions.colorScheme}"; window.safeAreaInsets=${JSON.stringify(nativeOptions.safeAreaInsets)}; ${ - // in development, explicitly set Urbit runtime configured window vars - DEFAULT_SHIP_LOGIN_URL || DEFAULT_TLON_LOGIN_EMAIL + DEV_LOCAL ? ` window.our="${ship}"; window.ship="${ship?.slice(1)}"; ` : '' } `} onLoad={() => { // Start a timeout in case the web app doesn't send the appLoaded message - setTimeout(() => webviewContext.setAppLoaded(true), 10_000); + setTimeout(() => setAppLoaded(true), 10_000); }} onShouldStartLoadWithRequest={({ url }) => { const parsedUrl = new URL(url); @@ -228,39 +257,6 @@ export const SingletonWebview = () => { onMessage={async ({ nativeEvent: { data } }) => handleMessage(JSON.parse(data) as WebAppCommand) } - // on iOS, this is called if the webview crashes or is - // killed by the OS - onContentProcessDidTerminate={(event) => { - console.error('Content process terminated', event); - crashlytics().recordError( - new Error('Content process terminated, initiated recovery') - ); - webviewContext.setAppLoaded(false); - setCrashRecovery((prev) => ({ - ...prev, - isCrashed: true, - crashEvent: event, - })); - }} - // on Android, this is called if the webview crashes or is - // killed by the OS - onRenderProcessGone={(event) => { - console.error('Render process gone', event); - crashlytics().recordError( - new Error('Render process gone, initiated recovery') - ); - webviewContext.setAppLoaded(false); - setCrashRecovery((prev) => ({ - ...prev, - isCrashed: true, - crashEvent: event, - })); - }} - // store a reference to the last url the webview was at for crash recovery - onNavigationStateChange={({ url: rawUrl }) => { - const path = new URL(rawUrl).pathname; - setCrashRecovery((prev) => ({ ...prev, lastPath: trimFullPath(path) })); - }} /> ); }; diff --git a/apps/tlon-mobile/src/components/WebviewOverlay.tsx b/apps/tlon-mobile/src/components/WebviewOverlay.tsx deleted file mode 100644 index 67d7b5119d..0000000000 --- a/apps/tlon-mobile/src/components/WebviewOverlay.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { View } from '@tloncorp/ui'; - -import { useWebviewPositionContext } from '../contexts/webview/position'; -import { SingletonWebview } from './SingletonWebview'; - -export default function WebviewOverlay() { - const { position, visible } = useWebviewPositionContext(); - return ( - - - - ); -} diff --git a/apps/tlon-mobile/src/constants.ts b/apps/tlon-mobile/src/constants.ts index a5a9801f0f..f2b6000e6d 100644 --- a/apps/tlon-mobile/src/constants.ts +++ b/apps/tlon-mobile/src/constants.ts @@ -27,8 +27,6 @@ export const SHIP_URL_PATTERN = extra.shipUrlPattern ?? 'https://{shipId}.tlon.network'; export const DEFAULT_LURE = extra.defaultLure ?? '~nibset-napwyn/tlon'; export const DEFAULT_PRIORITY_TOKEN = extra.defaultPriorityToken ?? 'mobile'; -export const DEFAULT_TLON_LOGIN_EMAIL = extra.defaultTlonLoginEmail ?? ''; -export const DEFAULT_TLON_LOGIN_PASSWORD = extra.defaultTlonLoginPassword ?? ''; -export const DEFAULT_SHIP_LOGIN_URL = extra.defaultShipLoginUrl ?? ''; -export const DEFAULT_SHIP_LOGIN_ACCESS_CODE = - extra.defaultShipLoginAccessCode ?? ''; + +export const DEV_LOCAL = extra.devLocal ? Boolean(extra.devLocal) : false; +export const DEV_LOCAL_CODE = extra.devLocalCode ?? ''; diff --git a/apps/tlon-mobile/src/contexts/branch.tsx b/apps/tlon-mobile/src/contexts/branch.tsx deleted file mode 100644 index 7502d67c21..0000000000 --- a/apps/tlon-mobile/src/contexts/branch.tsx +++ /dev/null @@ -1,155 +0,0 @@ -import { - type ReactNode, - createContext, - useCallback, - useContext, - useEffect, - useState, -} from 'react'; -import branch from 'react-native-branch'; - -import storage from '../lib/storage'; -import { getPathFromWer } from '../utils/string'; - -type Lure = { - lure: string | undefined; - priorityToken: string | undefined; -}; - -type State = Lure & { - deepLinkPath: string | undefined; -}; - -type ContextValue = State & { - clearLure: () => void; - clearDeepLink: () => void; -}; - -const INITIAL_STATE: State = { - deepLinkPath: undefined, - lure: undefined, - priorityToken: undefined, -}; - -const STORAGE_KEY = 'lure'; - -const saveLure = async (lure: Lure) => - storage.save({ key: STORAGE_KEY, data: JSON.stringify(lure) }); - -const getSavedLure = async () => { - try { - const lureString = await storage.load({ - key: STORAGE_KEY, - }); - return lureString ? (JSON.parse(lureString) as Lure) : undefined; - } catch (err) { - return undefined; - } -}; - -const clearSavedLure = async () => storage.remove({ key: STORAGE_KEY }); - -const Context = createContext({} as ContextValue); - -export const useBranch = () => { - const context = useContext(Context); - - if (!context) { - throw new Error( - 'Must call `useBranch` within a `BranchProvider` component.' - ); - } - - return context; -}; - -export const BranchProvider = ({ children }: { children: ReactNode }) => { - const [{ deepLinkPath, lure, priorityToken }, setState] = - useState(INITIAL_STATE); - - useEffect(() => { - console.debug('[branch] Subscribing to Branch listener'); - - // Subscribe to Branch deep link listener - const unsubscribe = branch.subscribe({ - onOpenComplete: ({ params }) => { - // Handle Branch link click - if (params?.['+clicked_branch_link']) { - console.debug('[branch] Detected Branch link click'); - - if (params.lure) { - // Link had a lure field embedded - console.debug('[branch] Detected lure link:', params.lure); - const nextLure: Lure = { - lure: params.lure as string, - priorityToken: params.token as string | undefined, - }; - setState({ - ...nextLure, - deepLinkPath: undefined, - }); - saveLure(nextLure); - } else if (params.wer) { - // Link had a wer (deep link) field embedded - const deepLinkPath = getPathFromWer(params.wer as string); - console.debug('[branch] Detected deep link:', deepLinkPath); - setState({ - deepLinkPath, - lure: undefined, - priorityToken: undefined, - }); - } - } - }, - }); - - // Check for saved lure - (async () => { - const nextLure = await getSavedLure(); - if (nextLure) { - console.debug('[branch] Detected saved lure:', nextLure.lure); - setState({ - ...nextLure, - deepLinkPath: undefined, - }); - } - })(); - - return () => { - console.debug('[branch] Unsubscribing from Branch listener'); - unsubscribe(); - }; - }, []); - - const clearLure = useCallback(() => { - console.debug('[branch] Clearing lure state'); - setState((curr) => ({ - ...curr, - lure: undefined, - priorityToken: undefined, - })); - clearSavedLure(); - }, []); - - const clearDeepLink = useCallback(() => { - console.debug('[branch] Clearing deep link state'); - setState((curr) => ({ - ...curr, - deepLinkPath: undefined, - })); - }, []); - - return ( - - {children} - - ); -}; diff --git a/apps/tlon-mobile/src/contexts/webview/webview.tsx b/apps/tlon-mobile/src/contexts/webview/webview.tsx index 4866517e67..3ea8f2f768 100644 --- a/apps/tlon-mobile/src/contexts/webview/webview.tsx +++ b/apps/tlon-mobile/src/contexts/webview/webview.tsx @@ -1,57 +1,39 @@ import type { MobileNavTab, NativeCommand } from '@tloncorp/shared'; import { createContext, useContext, useState } from 'react'; -type ManageAccountState = 'initial' | 'triggered' | 'navigated'; - export interface WebviewContext { - // fired when the web app has finished loading and is ready to display - appLoaded: boolean; - setAppLoaded: (isLoaded: boolean) => void; - - // path the webapp should navigate to gotoPath: string; - setGotoPath: (gotoPath: string) => void; - clearGotoPath: () => void; - - // tab native we need to navigate to in response to web app navigation - newWebappTab: MobileNavTab | null; - setNewWebappTab: (gotoTab: MobileNavTab | null) => void; + gotoTab: MobileNavTab | null; reactingToWebappNav: boolean; - setReactingToWebappNav: (reacting: boolean) => void; - - // last location tracking for groups and messages tabs. Allows you - // to pickup where you left off when you navigate back to the tab lastMessagesPath: string; lastGroupsPath: string; - setLastMessagesPath: (path: string) => void; - setLastGroupsPath: (path: string) => void; - - // ipc between web app and native pendingCommand: NativeCommand | null; + didManageAccount: boolean; + setDidManageAccount: (didManageAccount: boolean) => void; sendCommand: (command: NativeCommand) => void; - - // handle account management flow which can be triggered from the web app - manageAccountState: ManageAccountState; - setManageAccountState: (didManageAccount: ManageAccountState) => void; + setLastMessagesPath: (path: string) => void; + setLastGroupsPath: (path: string) => void; + setReactingToWebappNav: (reacting: boolean) => void; + setGotoPath: (gotoPath: string) => void; + setGotoTab: (gotoTab: MobileNavTab | null) => void; + clearGotoPath: () => void; } const AppWebviewContext = createContext({ - appLoaded: false, gotoPath: '', - newWebappTab: null, + gotoTab: null, reactingToWebappNav: false, lastMessagesPath: '', lastGroupsPath: '', pendingCommand: null, - manageAccountState: 'initial', - setAppLoaded: () => {}, - setManageAccountState: () => {}, + didManageAccount: false, + setDidManageAccount: () => {}, sendCommand: () => {}, setLastGroupsPath: () => {}, setLastMessagesPath: () => {}, setReactingToWebappNav: () => {}, setGotoPath: () => {}, - setNewWebappTab: () => {}, + setGotoTab: () => {}, clearGotoPath: () => {}, }); @@ -60,35 +42,31 @@ interface AppWebviewProviderProps { } export const WebviewProvider = ({ children }: AppWebviewProviderProps) => { - const [appLoaded, setAppLoaded] = useState(false); const [pendingCommand, sendCommand] = useState(null); const [lastMessagesPath, setLastMessagesPath] = useState(''); const [lastGroupsPath, setLastGroupsPath] = useState(''); const [reactingToWebappNav, setReactingToWebappNav] = useState(false); const [gotoPath, setGotoPath] = useState(''); - const [newWebappTab, setNewWebappTab] = useState(null); - const [manageAccountState, setManageAccountState] = - useState('initial'); + const [gotoTab, setGotoTab] = useState(null); + const [didManageAccount, setDidManageAccount] = useState(false); return ( setGotoPath(''), }} > diff --git a/apps/tlon-mobile/src/db/hooks.ts b/apps/tlon-mobile/src/db/hooks.ts deleted file mode 100644 index 909fef9ed2..0000000000 --- a/apps/tlon-mobile/src/db/hooks.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { ClientTypes as Client } from '@tloncorp/shared'; - -import { unreadChannelsQuery } from './queries'; -import { useObject, useQuery } from './realm'; - -// Model hooks -export function useContact(id: string): Client.Contact | null { - return useObject('Contact', id); -} - -export function useUnreadChannelsCount(): { - total: number; - groups: number; - dms: number; -} { - const groupUnreads = useQuery('Unread', unreadChannelsQuery('channel')); - const dmUnreads = useQuery('Unread', unreadChannelsQuery('dm')); - - return { - total: groupUnreads.length + dmUnreads.length, - groups: groupUnreads.length, - dms: dmUnreads.length, - }; -} diff --git a/apps/tlon-mobile/src/db/index.ts b/apps/tlon-mobile/src/db/index.ts index 04c418dfc5..ac2f66eea9 100644 --- a/apps/tlon-mobile/src/db/index.ts +++ b/apps/tlon-mobile/src/db/index.ts @@ -1,3 +1,2 @@ export * from './realm'; export * from './schemas'; -export * from './queries'; diff --git a/apps/tlon-mobile/src/db/queries.ts b/apps/tlon-mobile/src/db/queries.ts deleted file mode 100644 index 1aea7249ee..0000000000 --- a/apps/tlon-mobile/src/db/queries.ts +++ /dev/null @@ -1,87 +0,0 @@ -import type { ClientTypes as Client } from '@tloncorp/shared'; - -import { UpdateMode, realm } from './realm'; -import type { - SchemaMap, - SchemaModel, - SchemaName, - SchemaValue, -} from './schemas'; - -// Model queries - -export function unreadChannelsQuery( - type: Client.UnreadType -): QueryFn<'Unread'> { - return (collection: Results<'Unread'>) => { - return collection.filtered('type == $0 AND totalCount > 0', type); - }; -} - -// Generic queries - -export function createBatch( - model: T, - data: SchemaMap[T][], - updateMode = UpdateMode.Modified -): SchemaMap[T][] { - return batch(() => - data.map((d) => { - return realm.create(model, d, updateMode); - }) - ); -} - -export function create( - model: T, - data: SchemaMap[T], - updateMode = UpdateMode.Modified -): SchemaMap[T] { - return batch(() => realm.create(model, data, updateMode)); -} - -export function update( - model: T, - data: Partial, - updateMode = UpdateMode.Modified -): SchemaMap[T] { - return batch(() => realm.create(model, data, updateMode)); -} - -export function getObject( - model: T, - id: SchemaValue -): SchemaMap[T] | null { - return realm.objectForPrimaryKey(model, id); -} - -export type Results< - T extends SchemaName, - IsRealm extends boolean = false, -> = Realm.Results< - IsRealm extends true ? Realm.Object> : SchemaModel ->; - -export type QueryFn = ( - collection: Results -) => Results; - -export function getObjects( - schemaName: T, - query?: QueryFn -) { - const results = realm.objects(schemaName) as unknown as Results; - return query ? query(results) : results; -} - -/** - * Executes a function inside a realm write block. Unlike realm.write, this can - * be nested without causing issues. - */ -export function batch(cb: () => T) { - if (realm.isInTransaction) { - return cb(); - } else { - return realm.write(cb); - } -} diff --git a/apps/tlon-mobile/src/db/realm.tsx b/apps/tlon-mobile/src/db/realm.tsx index 63a098dfdd..3d46a4a48d 100644 --- a/apps/tlon-mobile/src/db/realm.tsx +++ b/apps/tlon-mobile/src/db/realm.tsx @@ -3,6 +3,7 @@ import React from 'react'; import type { PropsWithChildren } from 'react'; import type Realm from 'realm'; +import type { SchemaMap, SchemaName } from './schemas'; import { schemas } from './schemas'; // This is a copy of Realm's `UpdateMode` enum. Not ideal, but realm only @@ -30,22 +31,18 @@ const { let realmInstance: Realm | null = null; -export const realm = new Proxy( - {}, - { - get(_, prop) { - if (!realmInstance) throw new Error('Realm not initialized'); - return realmInstance[prop as keyof Realm]; - }, +function realm() { + if (!realmInstance) { + throw new Error('Realm instance not available'); } -) as Realm; + return realmInstance; +} // The only straightforward way to get the realm instance here is to use the // `realmRef` property. Since the property takes a ref, we use a proxy to // synchronously mirror the set value to the local `realm` variable. const realmRefProxy = { set current(val: Realm | null) { - console.log('Realm initialized at ' + val?.path); realmInstance = val; }, }; @@ -57,3 +54,37 @@ const RealmProvider = ({ children }: PropsWithChildren) => { }; export { RealmProvider, useObject, useQuery, useRealm }; + +// Utility functions + +export function createBatch( + model: T, + data: SchemaMap[T][], + updateMode = UpdateMode.Modified +): SchemaMap[T][] { + return realm().write(() => + data.map((d) => { + return realm().create(model, d, updateMode); + }) + ); +} + +export function create( + model: T, + data: SchemaMap[T], + updateMode = UpdateMode.Modified +): SchemaMap[T] { + return realm().write(() => + realm().create(model, data, updateMode) + ); +} + +export function update( + model: T, + data: Partial, + updateMode = UpdateMode.Modified +): SchemaMap[T] { + return realm().write(() => + realm().create(model, data, updateMode) + ); +} diff --git a/apps/tlon-mobile/src/db/schemas.ts b/apps/tlon-mobile/src/db/schemas.ts index 1fdc32ae09..3e446e8d9c 100644 --- a/apps/tlon-mobile/src/db/schemas.ts +++ b/apps/tlon-mobile/src/db/schemas.ts @@ -1,17 +1,13 @@ -import type { ClientTypes as Client } from '@tloncorp/shared'; - -export function fallbackContact(id: string): Client.Contact { - return { - id, - nickname: null, - bio: null, - status: null, - color: null, - avatarImage: null, - coverImage: null, - pinnedGroupIds: [], - }; -} +export type Contact = { + id: string; + nickname: string | null; + bio: string | null; + status: string | null; + color: string | null; + avatarImage: string | null; + coverImage: string | null; + pinnedGroupIds: string[]; +}; const contactSchema = { name: 'Contact', @@ -28,26 +24,12 @@ const contactSchema = { primaryKey: 'id', }; -const unreadSchema = { - name: 'Unread', - properties: { - channelId: 'string', - totalCount: 'int', - type: 'string', - }, - primaryKey: 'channelId', -}; - // Should contain all schemas, will be passed to Realm constructor -export const schemas = [contactSchema, unreadSchema]; +export const schemas = [contactSchema]; // Should contain all schema types, used to map Realm object types to TypeScript types export type SchemaMap = { - Contact: Client.Contact; - Unread: Client.Unread; + Contact: Contact; }; export type SchemaName = keyof SchemaMap; -export type SchemaModel = SchemaMap[T]; -export type SchemaKey = keyof SchemaModel; -export type SchemaValue = SchemaModel[SchemaKey]; diff --git a/apps/tlon-mobile/src/hooks/useAppStatus.ts b/apps/tlon-mobile/src/hooks/useAppStatus.ts deleted file mode 100644 index 6751b4eca3..0000000000 --- a/apps/tlon-mobile/src/hooks/useAppStatus.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { useEffect, useState } from 'react'; -import { AppState, type AppStateStatus } from 'react-native'; - -export default function useAppStatus() { - const [status, setStatus] = useState(AppState.currentState); - - useEffect(() => { - const subscription = AppState.addEventListener( - 'change', - (nextStatus: AppStateStatus) => setStatus(nextStatus) - ); - - return () => { - subscription.remove(); - }; - }, [status]); - - return status; -} diff --git a/apps/tlon-mobile/src/hooks/useDeepLink.ts b/apps/tlon-mobile/src/hooks/useDeepLink.ts new file mode 100644 index 0000000000..a092d6986f --- /dev/null +++ b/apps/tlon-mobile/src/hooks/useDeepLink.ts @@ -0,0 +1,110 @@ +import { useCallback, useEffect, useState } from 'react'; +import branch from 'react-native-branch'; + +import storage from '../lib/storage'; + +type Lure = { + lure: string | undefined; + priorityToken: string | undefined; +}; + +type DeepLink = Lure & { + wer: string | undefined; +}; + +const STORAGE_KEY = 'lure'; + +const INITIAL_DEEP_LINK: DeepLink = { + wer: undefined, + lure: undefined, + priorityToken: undefined, +}; + +const saveLure = async (lure: Lure) => + storage.save({ key: STORAGE_KEY, data: JSON.stringify(lure) }); + +const getSavedLure = async () => { + try { + const lureString = await storage.load({ + key: STORAGE_KEY, + }); + return lureString ? (JSON.parse(lureString) as Lure) : undefined; + } catch (err) { + return undefined; + } +}; + +const clearSavedLure = async () => storage.remove({ key: STORAGE_KEY }); + +export const useDeepLink = () => { + const [{ wer, lure, priorityToken }, setDeepLink] = + useState(INITIAL_DEEP_LINK); + + useEffect(() => { + // Branch deep linking listener + const unsubscribeFromBranch = branch.subscribe({ + onOpenComplete: ({ params }) => { + if (params?.['+clicked_branch_link']) { + console.debug('Detected Branch link click'); + if (params.lure) { + console.debug('Detected Lure link:', params.lure); + const nextLure: Lure = { + lure: params.lure as string, + priorityToken: params.token as string | undefined, + }; + setDeepLink({ + ...nextLure, + wer: undefined, + }); + saveLure(nextLure); + } else if (params.wer) { + console.debug('Detected deep link:', params.wer); + setDeepLink({ + wer: params.wer as string, + lure: undefined, + priorityToken: undefined, + }); + } + } + }, + }); + + // Check for saved lure + (async () => { + const nextLure = await getSavedLure(); + if (nextLure) { + console.debug('Detected saved Lure:', nextLure.lure); + setDeepLink({ + ...nextLure, + wer: undefined, + }); + } + })(); + + return () => { + unsubscribeFromBranch(); + }; + }, []); + + const clearDeepLink = useCallback(() => { + setDeepLink(INITIAL_DEEP_LINK); + clearSavedLure(); + }, []); + + const clearLure = useCallback(() => { + setDeepLink((curr) => ({ + ...curr, + lure: undefined, + priorityToken: undefined, + })); + clearSavedLure(); + }, []); + + return { + wer, + lure, + priorityToken, + clearDeepLink, + clearLure, + }; +}; diff --git a/apps/tlon-mobile/src/hooks/useDeepLinkListener.ts b/apps/tlon-mobile/src/hooks/useDeepLinkListener.ts deleted file mode 100644 index 20cb952b7f..0000000000 --- a/apps/tlon-mobile/src/hooks/useDeepLinkListener.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { useNavigation } from '@react-navigation/native'; -import type { NavigationProp } from '@react-navigation/native'; -import { parseActiveTab } from '@tloncorp/shared'; -import { useEffect } from 'react'; -import { Alert } from 'react-native'; - -import { useBranch } from '../contexts/branch'; -import { useShip } from '../contexts/ship'; -import { useWebViewContext } from '../contexts/webview/webview'; -import { inviteShipWithLure } from '../lib/hostingApi'; -import type { TabParamList } from '../types'; -import { trackError } from '../utils/posthog'; - -export const useDeepLinkListener = () => { - const navigation = useNavigation>(); - const webviewContext = useWebViewContext(); - const { ship } = useShip(); - const { lure, deepLinkPath, clearLure, clearDeepLink } = useBranch(); - - // If lure is present, invite it and mark as handled - useEffect(() => { - if (ship && lure) { - (async () => { - try { - await inviteShipWithLure({ ship, lure }); - Alert.alert( - '', - 'Your invitation to the group is on its way. It will appear in the Groups list.', - [ - { - text: 'OK', - onPress: () => null, - }, - ], - { cancelable: true } - ); - } catch (err) { - console.error( - '[useDeepLinkListener] Error inviting ship with lure:', - err - ); - if (err instanceof Error) { - trackError(err); - } - } - - clearLure(); - })(); - } - }, [ship, lure, clearLure]); - - // If deep link clicked, broadcast that navigation update to the webview and mark as handled - useEffect(() => { - if (deepLinkPath && webviewContext.appLoaded) { - console.debug( - '[useDeepLinkListener] Setting webview path:', - deepLinkPath - ); - webviewContext.setGotoPath(deepLinkPath); - const tab = parseActiveTab(deepLinkPath) ?? 'Groups'; - navigation.navigate(tab, { screen: 'Webview' }); - clearDeepLink(); - } - }, [deepLinkPath, webviewContext, navigation, clearDeepLink]); -}; diff --git a/apps/tlon-mobile/src/hooks/useLogout.ts b/apps/tlon-mobile/src/hooks/useLogout.ts deleted file mode 100644 index 60c1c9badc..0000000000 --- a/apps/tlon-mobile/src/hooks/useLogout.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { useCallback } from 'react'; - -import { useShip } from '../contexts/ship'; -import { removeUrbitClient } from '../lib/api'; -import { removeHostingToken, removeHostingUserId } from '../utils/hosting'; - -export function useLogout() { - const { clearShip } = useShip(); - const handleLogout = useCallback(() => { - clearShip(); - removeUrbitClient(); - removeHostingToken(); - removeHostingUserId(); - }, [clearShip]); - - return { handleLogout }; -} diff --git a/apps/tlon-mobile/src/hooks/useManageAccount.ts b/apps/tlon-mobile/src/hooks/useManageAccount.ts deleted file mode 100644 index de53761abb..0000000000 --- a/apps/tlon-mobile/src/hooks/useManageAccount.ts +++ /dev/null @@ -1,72 +0,0 @@ -import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; -import { useCallback, useEffect } from 'react'; - -import { useWebViewContext } from '../contexts/webview/webview'; -import { getHostingUser } from '../lib/hostingApi'; -import type { WebViewStackParamList } from '../types'; -import { getHostingToken, getHostingUserId } from '../utils/hosting'; -import { useLogout } from './useLogout'; - -export default function useManageAccount( - navigation: NativeStackNavigationProp -) { - const { manageAccountState, setManageAccountState } = useWebViewContext(); - const { handleLogout } = useLogout(); - - // If the user selected manage account, navigate to the external - // hosting page - const navigateToManageAccount = useCallback(async () => { - const [hostingSession, hostingUserId] = await Promise.all([ - getHostingToken(), - getHostingUserId(), - ]); - - navigation.push('ExternalWebView', { - uri: 'https://tlon.network/account', - headers: { - Cookie: hostingSession, - }, - injectedJavaScript: `localStorage.setItem("X-SESSION-ID", "${hostingUserId}")`, - }); - - setManageAccountState('navigated'); - }, [navigation, setManageAccountState]); - - // If we returned from managing the account, check if the user deleted - // their account & logout if so - const checkUserAccount = useCallback(async () => { - const hostingUserId = await getHostingUserId(); - if (hostingUserId) { - try { - const user = await getHostingUser(hostingUserId); - if (user.verified) { - return; - } else { - handleLogout(); - } - } catch (err) { - handleLogout(); - } - } - setManageAccountState('initial'); - }, [handleLogout, setManageAccountState]); - - useEffect(() => { - if (manageAccountState === 'triggered') { - console.log('hook: manage account trigger detected'); - navigateToManageAccount(); - } - - if (manageAccountState === 'navigated') { - console.log('hook: manage account navigated detected'); - checkUserAccount(); - } - }, [ - checkUserAccount, - handleLogout, - manageAccountState, - navigateToManageAccount, - navigation, - setManageAccountState, - ]); -} diff --git a/apps/tlon-mobile/src/hooks/useNotificationListener.ts b/apps/tlon-mobile/src/hooks/useNotificationListener.ts deleted file mode 100644 index ecdd4fd676..0000000000 --- a/apps/tlon-mobile/src/hooks/useNotificationListener.ts +++ /dev/null @@ -1,70 +0,0 @@ -import type { NavigationProp } from '@react-navigation/native'; -import { useNavigation } from '@react-navigation/native'; -import { parseActiveTab } from '@tloncorp/shared'; -import { addNotificationResponseReceivedListener } from 'expo-notifications'; -import { useEffect, useState } from 'react'; - -import { useWebViewContext } from '../contexts/webview/webview'; -import { markChatRead } from '../lib/chatApi'; -import { connectNotifications } from '../lib/notifications'; -import type { TabParamList } from '../types'; -import { getPathFromWer } from '../utils/string'; - -export default function useNotificationListener(initialNotifPath?: string) { - const navigation = useNavigation>(); - const webviewContext = useWebViewContext(); - const [gotoPath, setGotoPath] = useState( - initialNotifPath ?? null - ); - - // Start notifications prompt - useEffect(() => { - connectNotifications(); - }, []); - - useEffect(() => { - // Start notification tap listener - // This only seems to get triggered on iOS. Android handles the tap and other intents in native code. - const notificationTapListener = addNotificationResponseReceivedListener( - (response) => { - const { - actionIdentifier, - userText, - notification: { - request: { - content: { data }, - }, - }, - } = response; - if (actionIdentifier === 'markAsRead' && data.channelId) { - markChatRead(data.channelId); - } else if (actionIdentifier === 'reply' && userText) { - // TODO: Send reply - } else if (data.wer) { - const notifPath = getPathFromWer(data.wer); - setGotoPath(notifPath); - } - } - ); - - return () => { - // Clean up listeners - notificationTapListener.remove(); - }; - }, [navigation, webviewContext]); - - // If notification tapped, broadcast that navigation update to the - // webview and mark as handled - useEffect(() => { - if (gotoPath && webviewContext.appLoaded) { - console.debug( - '[useNotificationListener] Setting webview path:', - gotoPath - ); - webviewContext.setGotoPath(gotoPath); - const tab = parseActiveTab(gotoPath) ?? 'Groups'; - navigation.navigate(tab, { screen: 'Webview' }); - setGotoPath(null); - } - }, [gotoPath, webviewContext, navigation]); -} diff --git a/apps/tlon-mobile/src/lib/api.ts b/apps/tlon-mobile/src/lib/api.ts index 0460705ad9..2246243fde 100644 --- a/apps/tlon-mobile/src/lib/api.ts +++ b/apps/tlon-mobile/src/lib/api.ts @@ -1,5 +1,4 @@ import { deSig } from '@urbit/aura'; -import { Urbit } from '@urbit/http-api'; import { createHexString } from '../utils/string'; @@ -10,83 +9,6 @@ const config = { }; let lastEventId = 1; -let client: Urbit | null = null; - -export function initializeUrbitClient(shipName: string, shipUrl: string) { - client = new Urbit( - shipUrl, - undefined, - undefined, - (input, { ...init } = {}) => { - const headers = new Headers(init.headers); - // The urbit client is inconsistent about sending cookies, sometimes causing - // the server to send back a new, anonymous, cookie, which is sent on all - // subsequent requests and screws everything up. This ensures that explicit - // cookie headers are never set, delegating all cookie handling to the - // native http client. - headers.delete('Cookie'); - headers.delete('cookie'); - const newInit: RequestInit = { - ...init, - headers, - // Avoid setting credentials method for same reason as above. - credentials: undefined, - // @ts-expect-error This is used by the SSE polyfill to determine whether - // to stream the request. - reactNative: { textStreaming: true }, - }; - return fetch(input, newInit); - } - ); - client.ship = deSig(shipName); - client.verbose = true; - - client.on('status-update', (status) => { - console.log(`client status:`, status); - }); - - client.on('error', (error) => { - console.error('client error:', error); - }); -} - -export function removeUrbitClient() { - client = null; -} - -interface UrbitEndpoint { - app: string; - path: string; -} - -function printEndpoint(endpoint: UrbitEndpoint) { - return `${endpoint.app}${endpoint.path}`; -} - -// TODO: we need to harden this similar to tlon-web -export function subscribe( - endpoint: UrbitEndpoint, - handler: (update: T) => void -) { - if (!client) { - throw new Error('Tied to subscribe, but Urbit client is not initialized'); - } - - client.subscribe({ - app: endpoint.app, - path: endpoint.path, - event: (data: T) => { - console.debug( - `got subscription event on ${printEndpoint(endpoint)}:`, - data - ); - handler(data); - }, - err: (error) => { - console.error(`subscribe error on ${printEndpoint(endpoint)}:`, error); - }, - }); -} export const configureApi = (shipName: string, shipUrl: string) => { config.shipName = deSig(shipName); diff --git a/apps/tlon-mobile/src/lib/contactsApi.ts b/apps/tlon-mobile/src/lib/contactsApi.ts index 2cb5c2ab97..94972e857b 100644 --- a/apps/tlon-mobile/src/lib/contactsApi.ts +++ b/apps/tlon-mobile/src/lib/contactsApi.ts @@ -1,7 +1,7 @@ -import type { ClientTypes as Client } from '@tloncorp/shared'; import type * as ub from '@tloncorp/shared/dist/urbit/contact'; import { parseUx } from '@urbit/aura'; +import type * as db from '../db'; import { scry } from './api'; export const getContacts = async () => { @@ -12,9 +12,7 @@ export const getContacts = async () => { return toClientContacts(results); }; -export const toClientContacts = ( - contacts: ub.ContactRolodex -): Client.Contact[] => { +export const toClientContacts = (contacts: ub.ContactRolodex): db.Contact[] => { return Object.entries(contacts).map(([ship, contact]) => toClientContact(ship, contact) ); @@ -23,7 +21,7 @@ export const toClientContacts = ( export const toClientContact = ( id: string, contact: ub.Contact | null -): Client.Contact => { +): db.Contact => { return { id, nickname: contact?.nickname ?? null, diff --git a/apps/tlon-mobile/src/lib/hostingApi.ts b/apps/tlon-mobile/src/lib/hostingApi.ts index 3fcf0a776e..f6c4123ca7 100644 --- a/apps/tlon-mobile/src/lib/hostingApi.ts +++ b/apps/tlon-mobile/src/lib/hostingApi.ts @@ -15,11 +15,7 @@ import type { ReservedShip, User, } from '../types'; -import { - getHostingUserId, - setHostingToken, - setHostingUserId, -} from '../utils/hosting'; +import { setHostingToken, setHostingUserId } from '../utils/hosting'; type HostingError = { message: string; @@ -160,10 +156,6 @@ export const logInHostingUser = async (params: { return result as User; }; -export const isUsingTlonAuth = () => { - return Boolean(getHostingUserId() ?? false); -}; - export const getHostingUser = async (userId: string) => hostingFetch(`/v1/users/${userId}`); diff --git a/apps/tlon-mobile/src/lib/subscribe.ts b/apps/tlon-mobile/src/lib/subscribe.ts deleted file mode 100644 index 35756483e5..0000000000 --- a/apps/tlon-mobile/src/lib/subscribe.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { ClientTypes as Client } from '@tloncorp/shared'; - -import * as db from '../db'; -import { subscribeChannelUnreads, subscribeDMUnreads } from './unreadsApi'; - -export const subscribeUnreads = async () => { - async function handleUnreadUpdate(unread: Client.Unread) { - db.create('Unread', unread, db.UpdateMode.All); - console.log(`Updated an unread for ${unread.channelId}`); - } - - subscribeChannelUnreads(handleUnreadUpdate); - subscribeDMUnreads(handleUnreadUpdate); -}; diff --git a/apps/tlon-mobile/src/lib/sync.ts b/apps/tlon-mobile/src/lib/sync.ts index ab4ad5075a..dd4bcba663 100644 --- a/apps/tlon-mobile/src/lib/sync.ts +++ b/apps/tlon-mobile/src/lib/sync.ts @@ -1,22 +1,8 @@ import * as db from '../db'; import { getContacts } from './contactsApi'; -import { getChannelUnreads, getDMUnreads } from './unreadsApi'; export const syncContacts = async () => { const contacts = await getContacts(); db.createBatch('Contact', contacts, db.UpdateMode.All); console.log('Synced', contacts.length, 'contacts'); }; - -export const syncUnreads = async () => { - const [ - channelUnreads, - dmUnreads, - ] = await Promise.all([ - getChannelUnreads(), - getDMUnreads(), - ]); - db.createBatch('Unread', channelUnreads, db.UpdateMode.All); - db.createBatch('Unread', dmUnreads, db.UpdateMode.All); - console.log('Synced', channelUnreads.length + dmUnreads.length, 'unreads'); -}; diff --git a/apps/tlon-mobile/src/lib/unreadsApi.test.ts b/apps/tlon-mobile/src/lib/unreadsApi.test.ts deleted file mode 100644 index cd754aaf12..0000000000 --- a/apps/tlon-mobile/src/lib/unreadsApi.test.ts +++ /dev/null @@ -1,60 +0,0 @@ -import type { ClientTypes as Client } from '@tloncorp/shared'; -import type * as ubChan from '@tloncorp/shared/dist/urbit/channel'; -import type * as ubDM from '@tloncorp/shared/dist/urbit/dms'; -import { expect, test } from 'vitest'; - -import { toClientUnread, toClientUnreads } from './unreadsApi'; - -const inputUnread: [string, ubChan.Unread, Client.UnreadType] = [ - 'chat/~nibset-napwyn/commons', - { - unread: null, - count: 0, - recency: 1684342021902, - threads: {}, - }, - 'channel', -]; - -const expectedChannelUnread = { - channelId: 'chat/~nibset-napwyn/commons', - type: 'channel', - totalCount: 0, -}; - -test('converts a channel unread from server to client format', () => { - expect(toClientUnread(...inputUnread)).toStrictEqual(expectedChannelUnread); -}); - -test('converts an array of contacts from server to client format', () => { - expect( - toClientUnreads({ [inputUnread[0]]: inputUnread[1] }, inputUnread[2]) - ).toStrictEqual([expectedChannelUnread]); -}); - -const inputDMUnread: [string, ubDM.DMUnread, Client.UnreadType] = [ - 'dm/~pondus-latter', - { - unread: null, - count: 0, - recency: 1684342021902, - threads: {}, - }, - 'dm', -]; - -const expectedDMUnread = { - channelId: 'dm/~pondus-latter', - type: 'dm', - totalCount: 0, -}; - -test('converts a channel unread from server to client format', () => { - expect(toClientUnread(...inputDMUnread)).toStrictEqual(expectedDMUnread); -}); - -test('converts an array of channels from server to client format', () => { - expect( - toClientUnreads({ [inputDMUnread[0]]: inputDMUnread[1] }, inputDMUnread[2]) - ).toStrictEqual([expectedDMUnread]); -}); diff --git a/apps/tlon-mobile/src/lib/unreadsApi.ts b/apps/tlon-mobile/src/lib/unreadsApi.ts deleted file mode 100644 index 13d66d7cac..0000000000 --- a/apps/tlon-mobile/src/lib/unreadsApi.ts +++ /dev/null @@ -1,66 +0,0 @@ -import type { ClientTypes as Client } from '@tloncorp/shared'; -import type * as ubChan from '@tloncorp/shared/dist/urbit/channel'; -import type * as ubDM from '@tloncorp/shared/dist/urbit/dms'; - -import { scry, subscribe } from './api'; - -export const getChannelUnreads = async () => { - const results = await scry({ - app: 'channels', - path: '/unreads', - }); - return toClientUnreads(results, 'channel'); -}; - -export const getDMUnreads = async () => { - const results = await scry({ - app: 'chat', - path: '/unreads', - }); - return toClientUnreads(results, 'dm'); -}; - -export const subscribeChannelUnreads = ( - handler: (unread: Client.Unread) => Promise -) => { - subscribe( - { app: 'channels', path: '/unreads' }, - async (update) => { - const unread = toClientUnread(update.nest, update.unread, 'channel'); - handler(unread); - } - ); -}; - -export const subscribeDMUnreads = ( - handler: (unread: Client.Unread) => Promise -) => { - subscribe( - { app: 'chat', path: '/unreads' }, - async (update) => { - const unread = toClientUnread(update.whom, update.unread, 'dm'); - handler(unread); - } - ); -}; - -export const toClientUnreads = ( - unreads: ubChan.Unreads, - type: Client.UnreadType -): Client.Unread[] => { - return Object.entries(unreads).map(([nest, contact]) => - toClientUnread(nest, contact, type) - ); -}; - -export const toClientUnread = ( - nestOrWhom: string, - unread: ubChan.Unread, - type: Client.UnreadType -): Client.Unread => { - return { - channelId: nestOrWhom, - totalCount: unread.count, - type, - }; -}; diff --git a/apps/tlon-mobile/src/lib/useDevTools.ts b/apps/tlon-mobile/src/lib/useDevTools.ts new file mode 100644 index 0000000000..d6130e353a --- /dev/null +++ b/apps/tlon-mobile/src/lib/useDevTools.ts @@ -0,0 +1,44 @@ +import { useEffect } from 'react'; + +import { useShip } from '../contexts/ship'; +import { getShipFromCookie } from '../utils/ship'; +import { getLandscapeAuthCookie } from './landscapeApi'; + +const DEV_SHIP_URL = 'http://localhost:3000'; + +export function useDevTools(config: { enabled: boolean; localCode: string }) { + const { setShip, clearShip } = useShip(); + + // For use with local development. If the env vars DEV_LOCAL and DEV_LOCAL_CODE are set, + // will attempt to automatically authenticate you to the tlon-web dev server + useEffect(() => { + async function setupDevAuth() { + let cookie = null; + try { + cookie = await getLandscapeAuthCookie(DEV_SHIP_URL, config.localCode); + } catch (e) { + console.error('Error getting development auth cookie:', e); + } + + if (cookie) { + const ship = getShipFromCookie(cookie); + setShip({ + ship, + shipUrl: DEV_SHIP_URL, + }); + console.log(`Development auth configured for ${ship}`); + } else { + console.warn('Failed to set up development auth'); + clearShip(); + } + } + + if (config.enabled) { + if (!config.localCode) { + console.warn('No code found, skipping development auth'); + return; + } + setupDevAuth(); + } + }, []); +} diff --git a/apps/tlon-mobile/src/navigation/TabStack.tsx b/apps/tlon-mobile/src/navigation/TabStack.tsx index e8df01f5d4..70380d16e2 100644 --- a/apps/tlon-mobile/src/navigation/TabStack.tsx +++ b/apps/tlon-mobile/src/navigation/TabStack.tsx @@ -1,10 +1,15 @@ import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; -import { Avatar, Circle, Icon, View } from '@tloncorp/ui'; +import { Icon, UrbitSigil, View, ZStack } from '@tloncorp/ui'; import type { IconType } from '@tloncorp/ui'; +import { SingletonWebview } from '../components/SingletonWebview'; import { useShip } from '../contexts/ship'; -import { fallbackContact } from '../db'; -import { useContact, useUnreadChannelsCount } from '../db/hooks'; +import { + WebviewPositionProvider, + useWebviewPositionContext, +} from '../contexts/webview/position'; +import { WebviewProvider } from '../contexts/webview/webview'; +import { getInitialPath } from '../lib/WebAppHelpers'; import type { TabParamList } from '../types'; import { WebViewStack } from './WebViewStack'; @@ -12,106 +17,122 @@ const Tab = createBottomTabNavigator(); export const TabStack = () => { const { ship } = useShip(); - const unreadCount = useUnreadChannelsCount(); - + const shipIsPlanetOrLarger = ship && ship.length <= 14; return ( - - ( - 0} + + + + + ( + + ), + tabBarShowLabel: false, + }} /> - ), - tabBarShowLabel: false, - }} - /> - ( - 0} + ( + + ), + tabBarShowLabel: false, + }} /> - ), - tabBarShowLabel: false, - }} - /> - ( - ( + + ), + tabBarShowLabel: false, + }} /> - ), - tabBarShowLabel: false, - }} - /> - ( - - ), - tabBarShowLabel: false, - }} - /> - - ); -}; + + ship && shipIsPlanetOrLarger ? ( + + ) : ( + + ), -function AvatarTabIcon({ id, focused }: { id: string; focused: boolean }) { - const contact = useContact(id); - return ( - + tabBarShowLabel: false, + }} + /> + + + + + + ); -} +}; function TabIcon({ type, activeType, isActive, - hasUnreads = false, }: { type: IconType; activeType?: IconType; isActive: boolean; - hasUnreads?: boolean; }) { const resolvedType = isActive && activeType ? activeType : type; return ( - - - - - - + + ); +} + +function WebviewOverlay() { + const { position, visible } = useWebviewPositionContext(); + return ( + + ); } diff --git a/apps/tlon-mobile/src/navigation/WebViewStack.tsx b/apps/tlon-mobile/src/navigation/WebViewStack.tsx index 34fbc99ef4..d96b380811 100644 --- a/apps/tlon-mobile/src/navigation/WebViewStack.tsx +++ b/apps/tlon-mobile/src/navigation/WebViewStack.tsx @@ -27,9 +27,9 @@ export const WebViewStack = (props: Props) => { ); const { setGotoPath, - newWebappTab, + gotoTab, reactingToWebappNav, - setNewWebappTab, + setGotoTab, setReactingToWebappNav, lastGroupsPath, lastMessagesPath, @@ -59,7 +59,7 @@ export const WebViewStack = (props: Props) => { } // Else, go to the tab's initial location - setGotoPath(getInitialPath(props.route.name)); + setGotoPath(props.route.params.initialPath); }); return unsubscribe; @@ -105,25 +105,25 @@ export const WebViewStack = (props: Props) => { return; } - if (newWebappTab && newWebappTab !== props.route.name) { + if (gotoTab && gotoTab !== props.route.name) { // we have to register that we're reacting to webapp navigation // so we know not to return to the tab's default/last location (as with a tab click) setReactingToWebappNav(true); // navigate to the new active tab - props.navigation.navigate(newWebappTab as keyof TabParamList, { - screen: 'Webview', + props.navigation.navigate(gotoTab as keyof TabParamList, { + initialPath: getInitialPath(gotoTab), }); // clear the gotoTab since it's been handled - setNewWebappTab(null); + setGotoTab(null); } }, [ focused, - newWebappTab, + gotoTab, props.navigation, props.route.name, - setNewWebappTab, + setGotoTab, setReactingToWebappNav, ]); @@ -132,6 +132,7 @@ export const WebViewStack = (props: Props) => { diff --git a/apps/tlon-mobile/src/screens/ExternalWebViewScreen.tsx b/apps/tlon-mobile/src/screens/ExternalWebViewScreen.tsx index 2285b862dc..d5fc0436ca 100644 --- a/apps/tlon-mobile/src/screens/ExternalWebViewScreen.tsx +++ b/apps/tlon-mobile/src/screens/ExternalWebViewScreen.tsx @@ -1,8 +1,6 @@ import type { NativeStackScreenProps } from '@react-navigation/native-stack'; -import { useEffect } from 'react'; import { WebView } from 'react-native-webview'; -import { useWebviewPositionContext } from '../contexts/webview/position'; import { useWebView } from '../hooks/useWebView'; import type { WebViewStackParamList } from '../types'; @@ -14,14 +12,6 @@ export const ExternalWebViewScreen = ({ }, }: Props) => { const webViewProps = useWebView(); - const webviewPosition = useWebviewPositionContext(); - - useEffect(() => { - webviewPosition.setVisibility(false); - return () => { - webviewPosition.setVisibility(true); - }; - }, []); return ( { diff --git a/apps/tlon-mobile/src/screens/ShipLoginScreen.tsx b/apps/tlon-mobile/src/screens/ShipLoginScreen.tsx index 4c0d5a2786..f6fefc7d4e 100644 --- a/apps/tlon-mobile/src/screens/ShipLoginScreen.tsx +++ b/apps/tlon-mobile/src/screens/ShipLoginScreen.tsx @@ -6,11 +6,7 @@ import { useTailwind } from 'tailwind-rn'; import { HeaderButton } from '../components/HeaderButton'; import { LoadingSpinner } from '../components/LoadingSpinner'; -import { - ACCESS_CODE_REGEX, - DEFAULT_SHIP_LOGIN_ACCESS_CODE, - DEFAULT_SHIP_LOGIN_URL, -} from '../constants'; +import { ACCESS_CODE_REGEX } from '../constants'; import { useShip } from '../contexts/ship'; import { getLandscapeAuthCookie } from '../lib/landscapeApi'; import type { OnboardingStackParamList } from '../types'; @@ -38,12 +34,7 @@ export const ShipLoginScreen = ({ navigation }: Props) => { handleSubmit, formState: { errors }, setValue, - } = useForm({ - defaultValues: { - shipUrl: DEFAULT_SHIP_LOGIN_URL, - accessCode: DEFAULT_SHIP_LOGIN_ACCESS_CODE, - }, - }); + } = useForm(); const { setShip } = useShip(); const onSubmit = handleSubmit(async ({ shipUrl: rawShipUrl, accessCode }) => { diff --git a/apps/tlon-mobile/src/screens/TlonLoginScreen.tsx b/apps/tlon-mobile/src/screens/TlonLoginScreen.tsx index f31d476088..22d6d467aa 100644 --- a/apps/tlon-mobile/src/screens/TlonLoginScreen.tsx +++ b/apps/tlon-mobile/src/screens/TlonLoginScreen.tsx @@ -6,10 +6,6 @@ import { useTailwind } from 'tailwind-rn'; import { HeaderButton } from '../components/HeaderButton'; import { LoadingSpinner } from '../components/LoadingSpinner'; -import { - DEFAULT_TLON_LOGIN_EMAIL, - DEFAULT_TLON_LOGIN_PASSWORD, -} from '../constants'; import { useShip } from '../contexts/ship'; import { getShipAccessCode, @@ -39,12 +35,7 @@ export const TlonLoginScreen = ({ navigation }: Props) => { handleSubmit, formState: { errors }, getValues, - } = useForm({ - defaultValues: { - email: DEFAULT_TLON_LOGIN_EMAIL, - password: DEFAULT_TLON_LOGIN_PASSWORD, - }, - }); + } = useForm(); const { setShip } = useShip(); const handleForgotPassword = () => { diff --git a/apps/tlon-mobile/src/screens/WebviewPlaceholderScreen.tsx b/apps/tlon-mobile/src/screens/WebviewPlaceholderScreen.tsx index 291426735e..564ced0c45 100644 --- a/apps/tlon-mobile/src/screens/WebviewPlaceholderScreen.tsx +++ b/apps/tlon-mobile/src/screens/WebviewPlaceholderScreen.tsx @@ -4,18 +4,14 @@ import { KeyboardAvoidingView, View } from 'react-native'; import { IS_IOS } from '../constants'; import { useWebviewPositionContext } from '../contexts/webview/position'; -import useManageAccount from '../hooks/useManageAccount'; import type { WebViewStackParamList } from '../types'; export type Props = NativeStackScreenProps; -export const WebviewPlaceholderScreen = (props: Props) => { +export const WebviewPlaceholderScreen = () => { const screenRef = useRef(null); const { setPosition } = useWebviewPositionContext(); - // Handles navigation if the web app tries to manage the Tlon account - useManageAccount(props.navigation); - // Pass along tab dimensions so webview can properly position const measureTab = useCallback(() => { screenRef.current?.measure((x, y, width, height) => { diff --git a/apps/tlon-mobile/src/types.ts b/apps/tlon-mobile/src/types.ts index 8fe6d8cd63..f4a1158aee 100644 --- a/apps/tlon-mobile/src/types.ts +++ b/apps/tlon-mobile/src/types.ts @@ -1,28 +1,29 @@ -import type { NavigatorScreenParams } from '@react-navigation/native'; - export type SignUpExtras = { nickname?: string; notificationToken?: string; telemetry?: boolean; }; -type ExternalWebViewScreenParams = { - uri: string; - headers?: Record; - injectedJavaScript?: string; +type WebViewScreenParams = { + initialPath: string; + gotoPath?: string; }; export type WebViewStackParamList = { - Webview: undefined; - ExternalWebView: ExternalWebViewScreenParams; + Webview: WebViewScreenParams; + ExternalWebView: { + uri: string; + headers?: Record; + injectedJavaScript?: string; + }; }; export type TabParamList = { - Groups: NavigatorScreenParams; - Messages: NavigatorScreenParams; - Activity: NavigatorScreenParams; - Profile: NavigatorScreenParams; - Discover: NavigatorScreenParams; + Groups: WebViewScreenParams; + Messages: WebViewScreenParams; + Activity: WebViewScreenParams; + Profile: WebViewScreenParams; + Discover: WebViewScreenParams; }; export type TabName = keyof TabParamList; diff --git a/apps/tlon-web/e2e/shipManifest.json b/apps/tlon-web/e2e/shipManifest.json index 780222050c..5034f7a65c 100644 --- a/apps/tlon-web/e2e/shipManifest.json +++ b/apps/tlon-web/e2e/shipManifest.json @@ -7,7 +7,7 @@ "code": "lidlut-tabwed-pillex-ridrup", "httpPort": "35453", "loopbackPort": "", - "webUrl": "http://127.0.0.1:3000" + "webUrl": "http://localhost:3000" }, "~bus": { "authFile": "e2e/.auth/bus.json", @@ -17,7 +17,7 @@ "code": "riddec-bicrym-ridlev-pocsef", "httpPort": "36963", "loopbackPort": "", - "webUrl": "http://127.0.0.1:3001" + "webUrl": "http://localhost:3001" }, "~habduc-patbud": { "authFile": "e2e/.auth/habduc-patbud.json", @@ -27,7 +27,7 @@ "code": "bisrym-lomwep-binbyr-tidwed", "httpPort": "35553", "loopbackPort": "", - "webUrl": "http://127.0.0.1:3000" + "webUrl": "http://localhost:3000" }, "~naldeg-mardev": { "authFile": "e2e/.auth/naldeg-mardev.json", @@ -37,6 +37,6 @@ "code": "hadbyn-savfel-lanlur-ronped", "httpPort": "36663", "loopbackPort": "", - "webUrl": "http://127.0.0.1:3001" + "webUrl": "http://localhost:3001" } } diff --git a/apps/tlon-web/package.json b/apps/tlon-web/package.json index e62cdc6c79..684475640c 100644 --- a/apps/tlon-web/package.json +++ b/apps/tlon-web/package.json @@ -45,6 +45,7 @@ "@aws-sdk/s3-request-presigner": "^3.190.0", "@emoji-mart/data": "^1.0.6", "@emoji-mart/react": "^1.0.1", + "@faker-js/faker": "^6.3.1", "@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-collapsible": "^1.0.3", "@radix-ui/react-dialog": "^1.0.4", @@ -91,7 +92,7 @@ "@types/marked": "^4.3.0", "@urbit/api": "^2.2.0", "@urbit/aura": "^1.0.0", - "@urbit/http-api": "^3.1.0-dev-2", + "@urbit/http-api": "^3.0.0", "@urbit/sigil-js": "^2.1.0", "any-ascii": "^0.3.1", "big-integer": "^1.6.51", @@ -164,7 +165,6 @@ "zustand": "^3.7.2" }, "devDependencies": { - "@faker-js/faker": "^8.4.1", "@playwright/test": "^1.33.0", "@tailwindcss/aspect-ratio": "^0.4.0", "@tailwindcss/container-queries": "^0.1.0", @@ -194,9 +194,9 @@ "@typescript-eslint/eslint-plugin": "^5.19.0", "@typescript-eslint/parser": "^5.19.0", "@urbit/eslint-config": "^1.0.3", - "@urbit/vite-plugin-urbit": "^2.0.1", - "@vitejs/plugin-basic-ssl": "^1.1.0", - "@vitejs/plugin-react": "^4.2.1", + "@urbit/vite-plugin-urbit": "^0.8.0", + "@vitejs/plugin-basic-ssl": "^1.0.1", + "@vitejs/plugin-react": "^4.0.3", "@welldone-software/why-did-you-render": "^7.0.1", "autoprefixer": "^10.4.4", "concurrently": "^8.0.1", @@ -228,12 +228,11 @@ "tailwindcss": "^3.2.7", "tailwindcss-theme-swapper": "^0.7.3", "tar-fs": "^3.0.4", - "tsc-files": "^1.1.4", - "typescript": "^5.4.2", - "vite": "^5.1.6", - "vite-plugin-pwa": "^0.17.5", - "vitest": "^0.34.1", - "workbox-window": "^7.0.0" + "tsc-files": "^1.1.3", + "typescript": "^4.6.3", + "vite": "^4.4.3", + "vite-plugin-pwa": "^0.14.4", + "vitest": "^0.34.1" }, "msw": { "workerDirectory": "./public" diff --git a/apps/tlon-web/playwright.config.ts b/apps/tlon-web/playwright.config.ts index f0f22424a9..feb1879a7e 100644 --- a/apps/tlon-web/playwright.config.ts +++ b/apps/tlon-web/playwright.config.ts @@ -96,13 +96,13 @@ export default defineConfig({ command: `cross-env SHIP_URL=${ (shipManifest as Record)['~habduc-patbud'].url } npm run dev-no-ssl`, - url: 'http://127.0.0.1:3000/apps/groups/', + url: 'http://localhost:3000/apps/groups/', }, { command: `cross-env SHIP_URL=${ (shipManifest as Record)['~naldeg-mardev'].url } E2E_PORT_3001=true npm run dev-no-ssl`, - url: 'http://127.0.0.1:3001/apps/groups/', + url: 'http://localhost:3001/apps/groups/', }, ], timeout: 60 * 1000, diff --git a/apps/tlon-web/rube/index.ts b/apps/tlon-web/rube/index.ts index e1fdf26704..93a2ed2793 100644 --- a/apps/tlon-web/rube/index.ts +++ b/apps/tlon-web/rube/index.ts @@ -41,7 +41,6 @@ const ships: Record< httpPort: string; loopbackPort: string; webUrl: string; - galaxy: boolean; } > = { zod: { @@ -51,7 +50,6 @@ const ships: Record< shipManifest['~zod'].downloadUrl.split('/').pop()! ), // eslint-disable-line extractPath: path.join(__dirname, 'zod'), - galaxy: true, }, bus: { ...shipManifest['~bus'], @@ -60,7 +58,6 @@ const ships: Record< shipManifest['~bus'].downloadUrl.split('/').pop()! ), // eslint-disable-line extractPath: path.join(__dirname, 'bus'), - galaxy: true, }, 'habduc-patbud': { ...shipManifest['~habduc-patbud'], @@ -69,7 +66,6 @@ const ships: Record< shipManifest['~habduc-patbud'].downloadUrl.split('/').pop()! ), // eslint-disable-line extractPath: path.join(__dirname, 'habduc-patbud'), - galaxy: false, }, 'naldeg-mardev': { ...shipManifest['~naldeg-mardev'], @@ -78,7 +74,6 @@ const ships: Record< shipManifest['~naldeg-mardev'].downloadUrl.split('/').pop()! ), // eslint-disable-line extractPath: path.join(__dirname, 'naldeg-mardev'), - galaxy: false, }, }; @@ -276,7 +271,7 @@ const copyDesks = async () => { const groups = path.resolve(__dirname, '../../../../desk'); for (const ship of Object.values(ships)) { - if (ship.galaxy || (targetShip && targetShip !== ship.ship)) { + if (targetShip && targetShip !== ship.ship) { continue; } @@ -402,7 +397,7 @@ const mountDesks = async () => { console.log('Mounting desks on fake ships'); for (const ship of Object.values(ships)) { - if (ship.galaxy || (targetShip && targetShip !== ship.ship)) { + if (targetShip && targetShip !== ship.ship) { continue; } @@ -414,7 +409,7 @@ const commitDesks = async () => { console.log('Committing desks on fake ships'); for (const ship of Object.values(ships)) { - if (ship.galaxy || (targetShip && targetShip !== ship.ship)) { + if (targetShip && targetShip !== ship.ship) { continue; } @@ -509,7 +504,7 @@ const shipsAreReadyForTests = async () => { const shipsArray = Object.values(ships); const results = await Promise.all( shipsArray.map(async (ship) => { - if (ship.galaxy || (targetShip && targetShip !== ship.ship)) { + if (targetShip && targetShip !== ship.ship) { return true; } @@ -536,7 +531,7 @@ const shipsAreReadyForTests = async () => { const checkShipReadinessForTests = async () => new Promise((resolve, reject) => { - const maxAttempts = 30; + const maxAttempts = 10; let attempts = 0; const tryConnect = async () => { diff --git a/apps/tlon-web/src/api.ts b/apps/tlon-web/src/api.ts index 2de14bff6b..86d8364578 100644 --- a/apps/tlon-web/src/api.ts +++ b/apps/tlon-web/src/api.ts @@ -69,7 +69,6 @@ const Urbit = UrbitBase as new ( url: string, code?: string, desk?: string, - fetch?: typeof window.fetch, urlTransformer?: (someUrl: string, json: EyrePayload) => string ) => UrbitBase; @@ -110,7 +109,7 @@ class API { return this.client; } - this.client = new Urbit('', '', window.desk, undefined, hostingUrl); + this.client = new Urbit('', '', window.desk, hostingUrl); this.client.ship = window.ship; this.client.verbose = showDevTools; diff --git a/apps/tlon-web/src/chat/ChatChannel.tsx b/apps/tlon-web/src/chat/ChatChannel.tsx index 1087bd3d0e..57e26b70c9 100644 --- a/apps/tlon-web/src/chat/ChatChannel.tsx +++ b/apps/tlon-web/src/chat/ChatChannel.tsx @@ -19,7 +19,6 @@ import { useDragAndDrop } from '@/logic/DragAndDropContext'; import { useFullChannel } from '@/logic/channel'; import { useBottomPadding } from '@/logic/position'; import { useIsScrolling } from '@/logic/scroll'; -import useIsEditingMessage from '@/logic/useIsEditingMessage'; import useMedia, { useIsMobile } from '@/logic/useMedia'; import { useAddPostMutation, @@ -42,7 +41,7 @@ function ChatChannel({ title }: ViewProps) { idShip: string; idTime: string; }>(); - const [searchParams] = useSearchParams(); + const [searchParams, setSearchParams] = useSearchParams(); const groupFlag = useRouteGroup(); const chFlag = `${chShip}/${chName}`; const nest = `chat/${chFlag}`; @@ -61,7 +60,6 @@ function ChatChannel({ title }: ViewProps) { () => searchParams.get('replyTo'), [searchParams] ); - const isEditing = useIsEditingMessage(); const activeTab = useActiveTab(); const replyingWrit = useReplyPost(nest, chatReplyId); const scrollElementRef = useRef(null); @@ -147,7 +145,7 @@ function ChatChannel({ title }: ViewProps) { footer={
void; dropZoneId: string; - replyingWrit?: PostTuple | WritTuple | ReplyTuple; + replyingWrit?: PageTuple | WritTuple | ReplyTuple; isScrolling: boolean; } @@ -159,8 +157,7 @@ export default function ChatInput({ ); const threadParentId = useThreadParentId(whom); const [uploadError, setUploadError] = useState(null); - const [, setSearchParams] = useSearchParams(); - const isEditing = useIsEditingMessage(); + const [searchParams, setSearchParams] = useSearchParams(); const [replyCite, setReplyCite] = useState(); const groupFlag = useGroupFlag(); const { privacy } = useGroupPrivacy(groupFlag); @@ -181,16 +178,6 @@ export default function ChatInput({ const shipIsBlocked = useIsShipBlocked(whom); const shipHasBlockedUs = useShipHasBlockedUs(whom); const { mutate: unblockShip } = useUnblockShipMutation(); - const isDmOrMultiDM = useIsDmOrMultiDm(whom); - const myLastMessage = useMyLastMessage(whom, replying); - const lastMessageId = myLastMessage ? myLastMessage.seal.id : ''; - const lastMessageIdRef = useRef(lastMessageId); - - useEffect(() => { - if (lastMessageId && lastMessageId !== lastMessageIdRef.current) { - lastMessageIdRef.current = lastMessageId; - } - }, [lastMessageId]); const handleUnblockClick = useCallback(() => { unblockShip({ @@ -388,31 +375,6 @@ export default function ChatInput({ ] ); - const onUpArrow = useCallback( - ({ editor }: HandlerParams) => { - if ( - lastMessageIdRef.current && - !isEditing && - !editor.isDestroyed && - editor.isEmpty && - // don't allow editing of DM/Group DM messages until we support it - // on the backend. - !isDmOrMultiDM - ) { - setSearchParams( - { - edit: lastMessageIdRef.current, - }, - { replace: true } - ); - editor.commands.blur(); - return true; - } - return false; - }, - [isEditing, isDmOrMultiDM, setSearchParams] - ); - /** * !!! CAUTION !!! * @@ -434,7 +396,6 @@ export default function ChatInput({ [onSubmit] ), onUpdate: onUpdate.current, - onUpArrow, }); useEffect(() => { @@ -442,16 +403,15 @@ export default function ChatInput({ (autoFocus || replyCite) && !isMobile && messageEditor && - !messageEditor.isDestroyed && - !isEditing + !messageEditor.isDestroyed ) { // end brings the cursor to the end of the content messageEditor?.commands.focus('end'); } - }, [autoFocus, replyCite, isMobile, messageEditor, isEditing]); + }, [autoFocus, replyCite, isMobile, messageEditor]); useEffect(() => { - if (messageEditor && !messageEditor.isDestroyed && !isEditing) { + if (messageEditor && !messageEditor.isDestroyed) { messageEditor?.commands.setContent(draft); } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -588,10 +548,6 @@ export default function ChatInput({ [id, uploader] ); - if (isEditing && isMobile) { - return null; - } - // @ts-expect-error tsc is not tracking the type narrowing in the filter const imageBlocks: ChatImage[] = chatInfo.blocks.filter((b) => 'image' in b); // chatStoreLogger.log('ChatInputRender', id, chatInfo); diff --git a/apps/tlon-web/src/chat/ChatMessage/ChatMessage.tsx b/apps/tlon-web/src/chat/ChatMessage/ChatMessage.tsx index d40b871cbd..165a1bff00 100644 --- a/apps/tlon-web/src/chat/ChatMessage/ChatMessage.tsx +++ b/apps/tlon-web/src/chat/ChatMessage/ChatMessage.tsx @@ -1,11 +1,6 @@ /* eslint-disable react/no-unused-prop-types */ -import { Editor } from '@tiptap/react'; -import { - Post, - Story, - Unread, - constructStory, -} from '@tloncorp/shared/dist/urbit/channel'; +// eslint-disable-next-line import/no-cycle +import { Post, Story, Unread } from '@tloncorp/shared/dist/urbit/channel'; import { DMUnread } from '@tloncorp/shared/dist/urbit/dms'; import { daToUnix } from '@urbit/api'; import { BigInteger } from 'big-integer'; @@ -20,8 +15,7 @@ import React, { useState, } from 'react'; import { useInView } from 'react-intersection-observer'; -import { NavLink, useParams, useSearchParams } from 'react-router-dom'; -import { useEventListener } from 'usehooks-ts'; +import { NavLink, useParams } from 'react-router-dom'; import ChatContent from '@/chat/ChatContent/ChatContent'; import Author from '@/chat/ChatMessage/Author'; @@ -29,17 +23,12 @@ import ChatMessageOptions from '@/chat/ChatMessage/ChatMessageOptions'; import DateDivider from '@/chat/ChatMessage/DateDivider'; import ChatReactions from '@/chat/ChatReactions/ChatReactions'; import Avatar from '@/components/Avatar'; -import MessageEditor, { useMessageEditor } from '@/components/MessageEditor'; import UnreadIndicator from '@/components/Sidebar/UnreadIndicator'; -import CheckIcon from '@/components/icons/CheckIcon'; import DoubleCaretRightIcon from '@/components/icons/DoubleCaretRightIcon'; -import { JSONToInlines, diaryMixedToJSON } from '@/logic/tiptap'; import useLongPress from '@/logic/useLongPress'; import { useIsMobile } from '@/logic/useMedia'; import { useIsDmOrMultiDm, whomIsDm, whomIsMultiDm } from '@/logic/utils'; import { - useEditPostMutation, - useIsEdited, useMarkReadMutation, usePostToggler, useTrackedPostStatus, @@ -147,9 +136,6 @@ const ChatMessage = React.memo< }: ChatMessageProps, ref ) => { - const [searchParms, setSearchParams] = useSearchParams(); - const isEditing = searchParms.get('edit') === writ.seal.id; - const isEdited = useIsEdited(writ); const { seal, essay } = writ; const container = useRef(null); const { idShip, idTime } = useParams<{ @@ -172,7 +158,6 @@ const ChatMessage = React.memo< const { open: pickerOpen } = useChatDialog(whom, seal.id, 'picker'); const { mutate: markChatRead } = useMarkReadMutation(); const { mutate: markDmRead } = useMarkDmReadMutation(); - const { mutate: editPost } = useEditPostMutation(); const { isHidden: isMessageHidden } = useMessageToggler(seal.id); const { isHidden: isPostHidden } = usePostToggler(seal.id); const isHidden = useMemo( @@ -226,6 +211,11 @@ const ChatMessage = React.memo< author: window.our, sent: essay.sent, }); + // const msgStatus = useTrackedMessageStatus(seal.id); + // const status = useTrackedPostStatus({ + // author: window.our, + // sent: essay.sent, + // }); const isDelivered = msgStatus === 'delivered' && trackedPostStatus === 'delivered'; @@ -320,58 +310,6 @@ const ChatMessage = React.memo< isThreadOp, ]); - const onSubmit = useCallback( - async (editor: Editor) => { - // const now = Date.now(); - const editorJson = editor.getJSON(); - const inlineContent = JSONToInlines(editorJson); - const content = constructStory(inlineContent); - - if (content.length === 0) { - return; - } - - editPost({ - nest: `chat/${whom}`, - time: seal.id, - essay: { - ...essay, - author: window.our, - content, - }, - }); - - setSearchParams({}, { replace: true }); - }, - [editPost, whom, seal.id, essay, setSearchParams] - ); - - const messageEditor = useMessageEditor({ - whom: writ.seal.id, - content: diaryMixedToJSON(essay.content), - uploadKey: 'chat-editor-should-not-be-used-for-uploads', - allowMentions: true, - onEnter: useCallback( - ({ editor }) => { - onSubmit(editor); - return true; - }, - [onSubmit] - ), - }); - - useEventListener('keydown', (e) => { - if (e.key === 'Escape' && isEditing) { - setSearchParams({}, { replace: true }); - } - }); - - useEffect(() => { - if (messageEditor && !messageEditor.isDestroyed && isEditing) { - messageEditor.commands.focus('end'); - } - }, [isEditing, messageEditor]); - if (!writ) { return null; } @@ -402,12 +340,8 @@ const ChatMessage = React.memo< {newAuthor ? ( ) : null} -
- {isDelivered && !isEditing && ( +
+ {isDelivered && ( {format(unix, 'HH:mm')}
-
- {isEditing && messageEditor && !messageEditor.isDestroyed ? ( -
- {messageEditor && !messageEditor.isDestroyed && ( - - )} -
- Editing message - -
-
- ) : ( -
- {isHidden ? ( - +
+ {isHidden ? ( + + ) : essay.content ? ( + + ) : null} + {Object.keys(seal.reacts).length > 0 && ( + <> + - ) : essay.content ? ( - - ) : null} - {Object.keys(seal.reacts).length > 0 && ( - <> - - - - )} - {replyCount > 0 && !hideReplies ? ( - - cn( - 'default-focus group -ml-2 whitespace-nowrap rounded p-2 text-sm font-semibold text-gray-800', - isActive - ? 'is-active bg-gray-50 [&>div>div>.reply-avatar]:outline-gray-50' - : '', - isLinked - ? '[&>div>div>.reply-avatar]:outline-blue-100 dark:[&>div>div>.reply-avatar]:outline-blue-900' - : '' - ) - } - > -
-
- {replyAuthors.map((ship, i) => ( -
- -
- ))} -
+ + )} + {replyCount > 0 && !hideReplies ? ( + + cn( + 'default-focus group -ml-2 whitespace-nowrap rounded p-2 text-sm font-semibold text-gray-800', + isActive + ? 'is-active bg-gray-50 [&>div>div>.reply-avatar]:outline-gray-50' + : '', + isLinked + ? '[&>div>div>.reply-avatar]:outline-blue-100 dark:[&>div>div>.reply-avatar]:outline-blue-900' + : '' + ) + } + > +
+
+ {replyAuthors.map((ship, i) => ( +
+ +
+ ))} +
- - {replyCount} {replyCount > 1 ? 'replies' : 'reply'}{' '} + + {replyCount} {replyCount > 1 ? 'replies' : 'reply'}{' '} + + {unreadDisplay === 'thread' ? ( + + ) : null} + + + Last reply  - {unreadDisplay === 'thread' ? ( - - ) : null} - - - Last reply  - - - {lastReplyTime && - (isToday(lastReplyTime) - ? `${formatDistanceToNow(lastReplyTime)} ago` - : formatRelative(lastReplyTime, new Date()))} - + + {lastReplyTime && + (isToday(lastReplyTime) + ? `${formatDistanceToNow(lastReplyTime)} ago` + : formatRelative(lastReplyTime, new Date()))} -
-
- ) : null} -
- )} - -
- {!isDelivered && !isEditing && ( + +
+
+ ) : null} +
+
+ {!isDelivered && ( )} - {isEdited && !isEditing && ( - Edited - )}
- {isEditing && messageEditor && messageEditor.getText() !== '' && ( -
- -
- )}
); diff --git a/apps/tlon-web/src/chat/ChatMessage/ChatMessageOptions.tsx b/apps/tlon-web/src/chat/ChatMessage/ChatMessageOptions.tsx index c2d42b3de2..7b0bbd6322 100644 --- a/apps/tlon-web/src/chat/ChatMessage/ChatMessageOptions.tsx +++ b/apps/tlon-web/src/chat/ChatMessage/ChatMessageOptions.tsx @@ -24,7 +24,6 @@ import CopyIcon from '@/components/icons/CopyIcon'; import FaceIcon from '@/components/icons/FaceIcon'; import HashIcon from '@/components/icons/HashIcon'; import HiddenIcon from '@/components/icons/HiddenIcon'; -import PencilIcon from '@/components/icons/PencilSettingsIcon'; import VisibleIcon from '@/components/icons/VisibleIcon'; import XIcon from '@/components/icons/XIcon'; import { captureGroupsAnalyticsEvent } from '@/logic/analytics'; @@ -88,7 +87,7 @@ function ChatMessageOptions(props: { 'delete' ); const { chShip, chName } = useParams(); - const [searchParams, setSearchParams] = useSearchParams(); + const [, setSearchParams] = useSearchParams(); const { load: loadEmoji } = useEmoji(); const isMobile = useIsMobile(); const chFlag = `${chShip}/${chName}`; @@ -169,10 +168,6 @@ function ChatMessageOptions(props: { setSearchParams({ replyTo: seal.id }, { replace: true }); }, [seal, setSearchParams]); - const edit = useCallback(() => { - setSearchParams({ edit: seal.id }, { replace: true }); - }, [seal, setSearchParams]); - const startThread = () => { navigate(`message/${seal.id}`); }; @@ -249,10 +244,6 @@ function ChatMessageOptions(props: { const showReplyAction = !hideReply; const showCopyAction = !!groupFlag; const showDeleteAction = isAdmin || window.our === essay.author; - // don't allow editing of DM/Group DM messages until we support it - // on the backend. - // TODO: remove this check when backend supports it - const showEditAction = window.our === essay.author && !isDMorMultiDM; const reactionsCount = Object.keys(seal.reacts).length; const actions: Action[] = []; @@ -346,19 +337,6 @@ function ChatMessageOptions(props: { keepOpenOnClick: true, }); - if (showEditAction) { - actions.push({ - key: 'edit', - content: ( -
- - Edit Message -
- ), - onClick: edit, - }); - } - actions.push({ key: 'hide', onClick: isDMorMultiDM ? toggleMsg : togglePost, @@ -538,14 +516,6 @@ function ChatMessageOptions(props: { action={() => setDeleteOpen(true)} /> )} - {showEditAction && ( - } - label="Edit" - showTooltip - action={edit} - /> - )}
)} diff --git a/apps/tlon-web/src/chat/ChatScroller/ChatScroller.tsx b/apps/tlon-web/src/chat/ChatScroller/ChatScroller.tsx index 3fb4997509..c70ab869d3 100644 --- a/apps/tlon-web/src/chat/ChatScroller/ChatScroller.tsx +++ b/apps/tlon-web/src/chat/ChatScroller/ChatScroller.tsx @@ -1,5 +1,5 @@ import { Virtualizer, useVirtualizer } from '@tanstack/react-virtual'; -import { PostTuple, ReplyTuple } from '@tloncorp/shared/dist/urbit/channel'; +import { PageTuple, ReplyTuple } from '@tloncorp/shared/dist/urbit/channel'; import { WritTuple } from '@tloncorp/shared/dist/urbit/dms'; import { BigInteger } from 'big-integer'; import React, { @@ -168,7 +168,7 @@ const loaderPadding = { export interface ChatScrollerProps { whom: string; - messages: PostTuple[] | WritTuple[] | ReplyTuple[]; + messages: PageTuple[] | WritTuple[] | ReplyTuple[]; onAtTop?: () => void; onAtBottom?: () => void; isLoadingOlder: boolean; diff --git a/apps/tlon-web/src/chat/ChatThread/ChatThread.tsx b/apps/tlon-web/src/chat/ChatThread/ChatThread.tsx index e3947e82c3..e7c934b3f0 100644 --- a/apps/tlon-web/src/chat/ChatThread/ChatThread.tsx +++ b/apps/tlon-web/src/chat/ChatThread/ChatThread.tsx @@ -73,7 +73,6 @@ export default function ChatThread() { bigInt(idTime!), { memo: note.essay, - revision: note.revision, seal: { id: note.seal.id, 'parent-id': note.seal.id, diff --git a/apps/tlon-web/src/chat/ChatWindow.tsx b/apps/tlon-web/src/chat/ChatWindow.tsx index 14e9504c9b..82ac9c06a5 100644 --- a/apps/tlon-web/src/chat/ChatWindow.tsx +++ b/apps/tlon-web/src/chat/ChatWindow.tsx @@ -39,7 +39,7 @@ export default function ChatWindow({ const [searchParams, setSearchParams] = useSearchParams(); const { idTime } = useParams(); const scrollToId = useMemo( - () => searchParams.get('msg') || searchParams.get('edit') || idTime, + () => searchParams.get('msg') || idTime, [searchParams, idTime] ); const nest = `chat/${whom}`; diff --git a/apps/tlon-web/src/components/MessageEditor.tsx b/apps/tlon-web/src/components/MessageEditor.tsx index f7747f7072..c6bfc2cc4b 100644 --- a/apps/tlon-web/src/components/MessageEditor.tsx +++ b/apps/tlon-web/src/components/MessageEditor.tsx @@ -46,7 +46,6 @@ interface useMessageEditorParams { allowMentions?: boolean; onEnter: ({ editor }: HandlerParams) => boolean; onUpdate?: ({ editor }: HandlerParams) => void; - onUpArrow?: ({ editor }: HandlerParams) => boolean; } /** @@ -66,7 +65,6 @@ export function useMessageEditor({ allowMentions = false, onEnter, onUpdate, - onUpArrow, }: useMessageEditorParams) { const calm = useCalm(); const chatBlocks = useChatBlocks(whom); @@ -111,7 +109,7 @@ export function useMessageEditor({ const keyMapExt = useMemo( () => Shortcuts({ - Enter: ({ editor }) => onEnter({ editor } as HandlerParams), + Enter: ({ editor }) => onEnter({ editor } as any), 'Shift-Enter': ({ editor }) => editor.commands.first(({ commands }) => [ () => commands.newlineInCode(), @@ -119,11 +117,8 @@ export function useMessageEditor({ () => commands.liftEmptyBlock(), () => commands.splitBlock(), ]), - ArrowUp: onUpArrow - ? ({ editor }) => onUpArrow({ editor } as HandlerParams) - : () => false, }), - [onEnter, onUpArrow] + [onEnter] ); const extensions = [ diff --git a/apps/tlon-web/src/diary/DiaryChannel.tsx b/apps/tlon-web/src/diary/DiaryChannel.tsx index 5847b2de18..b8303160d7 100644 --- a/apps/tlon-web/src/diary/DiaryChannel.tsx +++ b/apps/tlon-web/src/diary/DiaryChannel.tsx @@ -1,5 +1,5 @@ import * as Toast from '@radix-ui/react-toast'; -import { PostTuple } from '@tloncorp/shared/dist/urbit/channel'; +import { PageTuple } from '@tloncorp/shared/dist/urbit/channel'; import { ViewProps } from '@tloncorp/shared/dist/urbit/groups'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { Helmet } from 'react-helmet'; @@ -138,7 +138,7 @@ function DiaryChannel({ title }: ViewProps) { return b.compare(a); }); - const itemContent = (i: number, [time, outline]: PostTuple) => ( + const itemContent = (i: number, [time, outline]: PageTuple) => (
{lastArrangedNote === time.toString() && ( diff --git a/apps/tlon-web/src/diary/DiaryList/DiaryGridView.tsx b/apps/tlon-web/src/diary/DiaryList/DiaryGridView.tsx index df7e732e1d..34c84bb7ec 100644 --- a/apps/tlon-web/src/diary/DiaryList/DiaryGridView.tsx +++ b/apps/tlon-web/src/diary/DiaryList/DiaryGridView.tsx @@ -1,4 +1,4 @@ -import { Post, PostTuple } from '@tloncorp/shared/dist/urbit/channel'; +import { PageTuple, Post } from '@tloncorp/shared/dist/urbit/channel'; import { RenderComponentProps, useInfiniteLoader, @@ -12,11 +12,11 @@ import DiaryGridItem from '@/diary/DiaryList/DiaryGridItem'; import { useIsMobile } from '@/logic/useMedia'; interface DiaryGridProps { - outlines: PostTuple[]; + outlines: PageTuple[]; loadOlderNotes: (atBottom: boolean) => void; } -const masonryItem = ({ data }: RenderComponentProps) => ( +const masonryItem = ({ data }: RenderComponentProps) => ( ); diff --git a/apps/tlon-web/src/groups/GroupJoinList.tsx b/apps/tlon-web/src/groups/GroupJoinList.tsx index ccb45cefc7..5394aa4aec 100644 --- a/apps/tlon-web/src/groups/GroupJoinList.tsx +++ b/apps/tlon-web/src/groups/GroupJoinList.tsx @@ -31,7 +31,6 @@ export function GroupJoinItem({ return ( open()} className={highlight ? 'bg-blue-soft dark:bg-blue-900' : ''} icon={ diff --git a/apps/tlon-web/src/heap/HeapChannel.tsx b/apps/tlon-web/src/heap/HeapChannel.tsx index 0c3729d846..44956ecb83 100644 --- a/apps/tlon-web/src/heap/HeapChannel.tsx +++ b/apps/tlon-web/src/heap/HeapChannel.tsx @@ -1,5 +1,5 @@ import * as Toast from '@radix-ui/react-toast'; -import { Post, PostTuple } from '@tloncorp/shared/dist/urbit/channel'; +import { PageTuple, Post } from '@tloncorp/shared/dist/urbit/channel'; import { ViewProps } from '@tloncorp/shared/dist/urbit/groups'; import bigInt from 'big-integer'; import { useCallback, useEffect, useMemo, useState } from 'react'; @@ -113,7 +113,7 @@ function HeapChannel({ title }: ViewProps) { [hasNextPage, fetchNextPage] ); - const computeItemKey = (_i: number, [time, _curio]: PostTuple) => + const computeItemKey = (_i: number, [time, _curio]: PageTuple) => time.toString(); const thresholds = { diff --git a/apps/tlon-web/src/logic/native.ts b/apps/tlon-web/src/logic/native.ts index 13193de886..74c6548233 100644 --- a/apps/tlon-web/src/logic/native.ts +++ b/apps/tlon-web/src/logic/native.ts @@ -13,9 +13,6 @@ import { useSafeAreaContext } from './SafeAreaContext'; export const isNativeApp = () => !!window.ReactNativeWebView; -export const isUsingTlonAuth = () => - window.nativeOptions?.isUsingTlonAuth ?? false; - const postJSONToNativeApp = (obj: Record) => { window.ReactNativeWebView?.postMessage(JSON.stringify(obj)); }; diff --git a/apps/tlon-web/src/logic/scroll.ts b/apps/tlon-web/src/logic/scroll.ts index 61ea79241d..8a028e54ec 100644 --- a/apps/tlon-web/src/logic/scroll.ts +++ b/apps/tlon-web/src/logic/scroll.ts @@ -1,8 +1,6 @@ import { throttle } from 'lodash'; import { RefObject, useCallback, useEffect, useRef, useState } from 'react'; -import useIsEditingMessage from './useIsEditingMessage'; - /** * Utility for tracking scrolling state. Caller should call `didScroll` whenever * a scroll event occurs. @@ -57,24 +55,14 @@ export function useInvertedScrollInteraction( scrollElementRef: RefObject, isInverted: boolean ) { - const isEditing = useIsEditingMessage(); - const isEditingRef = useRef(isEditing); - - useEffect(() => { - isEditingRef.current = isEditing; - }, [isEditing]); - useEffect(() => { const el = scrollElementRef.current; if (!isInverted || !el) return undefined; - const invertScrollWheel = (e: WheelEvent) => { el.scrollTop -= e.deltaY; e.preventDefault(); }; const invertSpaceAndArrows = (e: KeyboardEvent) => { - if (isEditingRef.current) return; - if (e.key === 'ArrowUp') { e.preventDefault(); el.scrollBy({ top: 30, behavior: e.repeat ? 'auto' : 'smooth' }); @@ -90,15 +78,13 @@ export function useInvertedScrollInteraction( }); } }; - el.addEventListener('wheel', invertScrollWheel, false); el.addEventListener('keydown', invertSpaceAndArrows, true); - return () => { el.removeEventListener('wheel', invertScrollWheel); el.removeEventListener('keydown', invertSpaceAndArrows); }; - }, [isInverted, scrollElementRef, isEditing]); + }, [isInverted, scrollElementRef]); } /** diff --git a/apps/tlon-web/src/logic/useIsEditingMessage.tsx b/apps/tlon-web/src/logic/useIsEditingMessage.tsx deleted file mode 100644 index e3c414e497..0000000000 --- a/apps/tlon-web/src/logic/useIsEditingMessage.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { useMemo } from 'react'; -import { useSearchParams } from 'react-router-dom'; - -export default function useIsEditingMessage() { - const [searchParams] = useSearchParams(); - const isEditing = useMemo(() => !!searchParams.get('edit'), [searchParams]); - - return isEditing; -} diff --git a/apps/tlon-web/src/logic/useShowTabBar.ts b/apps/tlon-web/src/logic/useShowTabBar.ts index 7539e1a47c..ef19b12887 100644 --- a/apps/tlon-web/src/logic/useShowTabBar.ts +++ b/apps/tlon-web/src/logic/useShowTabBar.ts @@ -1,10 +1,6 @@ -import { useSearchParams } from 'react-router-dom'; - import { useIsMobile } from './useMedia'; export default function useShowTabBar() { const isMobile = useIsMobile(); - const [searchParams] = useSearchParams(); - const isEditing = !!searchParams.get('edit'); - return isMobile && !window.nativeOptions?.hideTabBar && !isEditing; + return isMobile && !window.nativeOptions?.hideTabBar; } diff --git a/apps/tlon-web/src/logic/useSidebarSort.ts b/apps/tlon-web/src/logic/useSidebarSort.ts index ba5c193d4a..6dc0da4d54 100644 --- a/apps/tlon-web/src/logic/useSidebarSort.ts +++ b/apps/tlon-web/src/logic/useSidebarSort.ts @@ -106,24 +106,17 @@ export default function useSidebarSort({ records: Record, accessor: (k: string, v: T) => string, reverse = false - ): [string, T][] => { - // pre-compute values for comparison - const entries = Object.entries(records).map(([key, obj]) => ({ - key, - obj, - value: accessor(key, obj), - })); + ) => { + const entries = Object.entries(records); + entries.sort(([aKey, aObj], [bKey, bObj]) => { + const aVal = accessor(aKey, aObj); + const bVal = accessor(bKey, bObj); - // integrate reverse sorting logic into the comparison - const directionMultiplier = reverse ? -1 : 1; - - entries.sort((a, b) => { const sorter = sortOptions[sortFn] ?? sortOptions[RECENT_SORT]; - return directionMultiplier * sorter(a.value, b.value); + return sorter(aVal, bVal); }); - // map back to the original format - return entries.map(({ key, obj }) => [key, obj]); + return reverse ? entries.reverse() : entries; }, [sortFn, sortOptions] ); diff --git a/apps/tlon-web/src/manifest.ts b/apps/tlon-web/src/manifest.ts index 481e347b96..5bd0bbeabc 100644 --- a/apps/tlon-web/src/manifest.ts +++ b/apps/tlon-web/src/manifest.ts @@ -1,12 +1,10 @@ -import { ManifestOptions } from 'vite-plugin-pwa'; - -const manifest: Partial = { +export default { name: 'Tlon', description: 'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a peer-to-peer collaboration tool built on Urbit that provides a few simple basics that communities can shape into something unique to their needs.', short_name: 'Tlon', - start_url: '/apps/groups', - scope: '/apps/groups', + start_url: '/apps/groups/', + scope: '/apps/groups/', id: '/apps/groups/', icons: [ { @@ -36,5 +34,3 @@ const manifest: Partial = { background_color: '#ffffff', display: 'standalone', }; - -export default manifest; diff --git a/apps/tlon-web/src/mocks/chat.ts b/apps/tlon-web/src/mocks/chat.ts index 450a3db98b..629e8b5f44 100644 --- a/apps/tlon-web/src/mocks/chat.ts +++ b/apps/tlon-web/src/mocks/chat.ts @@ -1,5 +1,5 @@ /* eslint-disable import/no-cycle */ -import { faker } from '@faker-js/faker'; +import faker from '@faker-js/faker'; import { Post, Posts, diff --git a/apps/tlon-web/src/mocks/groups.ts b/apps/tlon-web/src/mocks/groups.ts index 4167e978c0..bbae045779 100644 --- a/apps/tlon-web/src/mocks/groups.ts +++ b/apps/tlon-web/src/mocks/groups.ts @@ -1,5 +1,5 @@ /* eslint-disable import/no-cycle */ -import { faker } from '@faker-js/faker'; +import faker from '@faker-js/faker'; import { Cordon, Gang, @@ -64,7 +64,7 @@ export function makeGroupPreview(privacy = 'public'): GroupPreview { cordon: makeCordon(privacy), time: Date.now(), meta: { - title: faker.company.name(), + title: faker.company.companyName(), description: faker.company.catchPhrase(), image: `#${randomColor()}`, cover: `#${randomColor()}`, @@ -341,7 +341,7 @@ export function createChannel(title: string) { } for (let i = 0; i < 20; i += 1) { - const group = createMockGroup(faker.company.name()); + const group = createMockGroup(faker.company.companyName()); for (let j = 0; j < 20; j += 1) { group.channels[`/chat/~zod/tlon${i}${j}`] = createChannel(j.toString()); diff --git a/apps/tlon-web/src/profiles/Profile.tsx b/apps/tlon-web/src/profiles/Profile.tsx index 5882785fb2..b4e12cd873 100644 --- a/apps/tlon-web/src/profiles/Profile.tsx +++ b/apps/tlon-web/src/profiles/Profile.tsx @@ -14,11 +14,7 @@ import LogOutIcon from '@/components/icons/LogOutIcon'; import PersonIcon from '@/components/icons/PersonIcon'; import ShareIcon from '@/components/icons/ShareIcon'; import TlonIcon from '@/components/icons/TlonIcon'; -import { - isNativeApp, - isUsingTlonAuth, - postActionToNativeApp, -} from '@/logic/native'; +import { isNativeApp, postActionToNativeApp } from '@/logic/native'; import { useBottomPadding } from '@/logic/position'; import { useIsMobile } from '@/logic/useMedia'; import { isHosted } from '@/logic/utils'; @@ -109,7 +105,7 @@ export default function Profile({ title }: ViewProps) { )} - {isNativeApp() && isHosted && isUsingTlonAuth() && ( + {isNativeApp() && isHosted && ( -
- - ) : ( -
- {isHidden ? ( - - ) : memo.content ? ( - - ) : null} - {seal.reacts && Object.keys(seal.reacts).length > 0 && ( - <> - - - - )} -
- )} +
- {!isDelivered && !isEditing && ( + {isHidden ? ( + + ) : memo.content ? ( + + ) : null} + {seal.reacts && Object.keys(seal.reacts).length > 0 && ( + <> + + + + )} +
+
+ {!isDelivered && ( )} - {isEdited && !isEditing && ( - Edited - )}
- {isEditing && messageEditor && messageEditor.getText() !== '' && ( -
- -
- )} ); diff --git a/apps/tlon-web/src/replies/ReplyMessageOptions.tsx b/apps/tlon-web/src/replies/ReplyMessageOptions.tsx index faa2bb51bc..871854f7b4 100644 --- a/apps/tlon-web/src/replies/ReplyMessageOptions.tsx +++ b/apps/tlon-web/src/replies/ReplyMessageOptions.tsx @@ -17,7 +17,6 @@ import CheckIcon from '@/components/icons/CheckIcon'; import CopyIcon from '@/components/icons/CopyIcon'; import FaceIcon from '@/components/icons/FaceIcon'; import HiddenIcon from '@/components/icons/HiddenIcon'; -import PencilIcon from '@/components/icons/PencilSettingsIcon'; import VisibleIcon from '@/components/icons/VisibleIcon'; import XIcon from '@/components/icons/XIcon'; import { captureGroupsAnalyticsEvent } from '@/logic/analytics'; @@ -89,7 +88,6 @@ export default function ReplyMessageOptions(props: { const { load: loadEmoji } = useEmoji(); const isMobile = useIsMobile(); const isDMorMultiDM = useIsDmOrMultiDm(whom); - const showEditAction = window.our === reply.memo.author && !isDMorMultiDM; const perms = usePerms(isDMorMultiDM ? `fake/nest/${whom}` : nest); const vessel = useVessel(groupFlag, window.our); const group = useGroup(groupFlag); @@ -172,10 +170,6 @@ export default function ReplyMessageOptions(props: { } }, [doCopy, isMobile, onOpenChange]); - const edit = useCallback(() => { - setSearchParams({ edit: seal.id }, { replace: true }); - }, [seal, setSearchParams]); - const setReplyParam = useCallback(() => { setSearchParams({ replyTo: seal.id }, { replace: true }); }, [seal, setSearchParams]); @@ -329,19 +323,6 @@ export default function ReplyMessageOptions(props: { }); } - if (showEditAction) { - actions.push({ - key: 'edit', - content: ( -
- - Edit Message -
- ), - onClick: edit, - }); - } - actions.push({ key: 'hide', onClick: isDMorMultiDM ? toggleMsg : togglePost, @@ -494,14 +475,6 @@ export default function ReplyMessageOptions(props: { action={() => setDeleteOpen(true)} /> )} - {showEditAction && ( - } - label="Edit" - showTooltip - action={edit} - /> - )} )} diff --git a/apps/tlon-web/src/state/channel/channel.ts b/apps/tlon-web/src/state/channel/channel.ts index c30fa81406..188bc9aa07 100644 --- a/apps/tlon-web/src/state/channel/channel.ts +++ b/apps/tlon-web/src/state/channel/channel.ts @@ -1,4 +1,4 @@ -import { useInfiniteQuery, useMutation } from '@tanstack/react-query'; +import { QueryKey, useInfiniteQuery, useMutation } from '@tanstack/react-query'; import { Action, Channel, @@ -14,15 +14,14 @@ import { HiddenPosts, Memo, Nest, + PageTuple, PagedPosts, Perm, Post, PostAction, PostDataResponse, PostEssay, - PostTuple, Posts, - Replies, Reply, ReplyMeta, ReplyTuple, @@ -32,18 +31,12 @@ import { UnreadUpdate, Unreads, newChatMap, - newPostTupleArray, } from '@tloncorp/shared/dist/urbit/channel'; -import { - PagedWrits, - Writ, - newWritTupleArray, -} from '@tloncorp/shared/dist/urbit/dms'; import { Flag } from '@tloncorp/shared/dist/urbit/hark'; import { daToUnix, decToUd, udToDec, unixToDa } from '@urbit/api'; import { Poke } from '@urbit/http-api'; import bigInt from 'big-integer'; -import _, { last } from 'lodash'; +import _ from 'lodash'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import create from 'zustand'; @@ -63,13 +56,10 @@ import { log, nestToFlag, stringToTa, - useIsDmOrMultiDm, whomIsFlag, } from '@/logic/utils'; import queryClient from '@/queryClient'; -// eslint-disable-next-line import/no-cycle -import ChatQueryKeys from '../chat/keys'; import { channelKey, infinitePostsKey, postKey } from './keys'; import shouldAddPostToCache from './util'; @@ -222,7 +212,7 @@ export function usePostsOnHost( const { data } = useReactQueryScry({ queryKey: [han, 'posts', 'live', flag], app: 'channels', - path: `/v1/${nest}/posts/newest/${STANDARD_MESSAGE_FETCH_PAGE_SIZE}/outline`, + path: `/${nest}/posts/newest/${STANDARD_MESSAGE_FETCH_PAGE_SIZE}/outline`, priority: 2, options: { cacheTime: 0, @@ -605,13 +595,13 @@ export const infinitePostQueryFn = if (pageParam) { const { time, direction } = pageParam; const ud = decToUd(time); - path = `/v1/${nest}/posts/${direction}/${ud}/${POST_PAGE_SIZE}/outline`; + path = `/${nest}/posts/${direction}/${ud}/${POST_PAGE_SIZE}/outline`; } else if (initialTime) { - path = `/v1/${nest}/posts/around/${decToUd(initialTime)}/${ + path = `/${nest}/posts/around/${decToUd(initialTime)}/${ POST_PAGE_SIZE / 2 }/outline`; } else { - path = `/v1/${nest}/posts/newest/${POST_PAGE_SIZE}/outline`; + path = `/${nest}/posts/newest/${POST_PAGE_SIZE}/outline`; } const response = await api.scry({ @@ -660,7 +650,32 @@ export function useInfinitePosts(nest: Nest, initialTime?: string) { retry: false, }); - const posts = newPostTupleArray(data); + // we stringify the data here so that we can use it in useMemo's dependency array. + // this is because the data object is a reference and react will not + // do a deep comparison on it. + const stringifiedData = data ? JSON.stringify(data) : JSON.stringify({}); + + const posts: PageTuple[] = useMemo(() => { + if (data === undefined || data.pages.length === 0) { + return []; + } + + return _.uniqBy( + data.pages + .map((page) => { + const pagePosts = Object.entries(page.posts).map( + ([k, v]) => [bigInt(udToDec(k)), v] as PageTuple + ); + + return pagePosts; + }) + .flat(), + ([k]) => k.toString() + ).sort(([a], [b]) => a.compare(b)); + // we disable exhaustive deps here because we add stringifiedData + // to the dependency array to force a re-render when the data changes. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [stringifiedData, data]); return { data, @@ -699,7 +714,7 @@ export async function prefetchPostWithComments({ const [han] = nestToFlag(nest); const data = (await api.scry({ app: 'channels', - path: `/v1/${nest}/posts/post/${ud}`, + path: `/${nest}/posts/post/${ud}`, })) as Post; if (data) { queryClient.setQueryData([han, nest, 'posts', time, 'withComments'], data); @@ -864,7 +879,7 @@ export function useChannelsFirehose() { useEffect(() => { api.subscribe({ app: 'channels', - path: '/v1', + path: '/', event: eventHandler, }); }, [eventHandler]); @@ -952,11 +967,11 @@ export function usePost(nest: Nest, postId: string, disabled = false) { ); const scryPath = useMemo( - () => `/v1/${nest}/posts/post/${decToUd(postId)}`, + () => `/${nest}/posts/post/${decToUd(postId)}`, [nest, postId] ); - const subPath = useMemo(() => `/v1/${nest}`, [nest]); + const subPath = useMemo(() => `/${nest}`, [nest]); const enabled = useMemo( () => postId !== '0' && postId !== '' && nest !== '' && !disabled, @@ -1224,7 +1239,7 @@ export function useJoinMutation() { channelAction(chan, { join: group, }), - { app: 'channels', path: '/v1' }, + { app: 'channels', path: '/' }, (event) => event.nest === chan && 'create' in event.response ); }; @@ -1390,7 +1405,7 @@ export function useAddPostMutation(nest: string) { channelPostAction(nest, { add: variables.essay, }), - { app: 'channels', path: `/v1/${nest}` }, + { app: 'channels', path: `/${nest}` }, ({ response }) => { if ('post' in response) { const { id, 'r-post': postResponse } = response.post; @@ -1615,7 +1630,7 @@ export function useDeletePostMutation() { channelPostAction(variables.nest, { del: variables.time }), { app: 'channels', - path: `/v1/${variables.nest}`, + path: `/${variables.nest}`, }, (event) => { if ('post' in event.response) { @@ -1709,7 +1724,7 @@ export function useCreateMutation() { create: variables, }, }, - { app: 'channels', path: '/v1' }, + { app: 'channels', path: '/' }, (event) => { const { response, nest } = event; return ( @@ -1956,70 +1971,6 @@ export function useAddReplyMutation() { }); } -export function useEditReplyMutation() { - const mutationFn = async (variables: { - nest: Nest; - postId: string; - replyId: string; - memo: Memo; - }) => { - checkNest(variables.nest); - - const replying = decToUd(variables.postId); - const action: Action = { - post: { - reply: { - id: replying, - action: { - edit: { - id: decToUd(variables.replyId), - memo: variables.memo, - }, - }, - }, - }, - }; - - await api.poke(channelAction(variables.nest, action)); - }; - - return useMutation({ - mutationFn, - onMutate: async (variables) => { - const updater = (prevPost: PostDataResponse | undefined) => { - if (prevPost === undefined) { - return prevPost; - } - - const replyId = decToUd(variables.replyId); - - const prevReplies = prevPost.seal.replies; - const newReplies = { ...prevReplies }; - newReplies[replyId] = { - seal: { - id: variables.replyId, - 'parent-id': variables.postId, - reacts: {}, - }, - memo: variables.memo, - }; - - const updatedPost: PostDataResponse = { - ...prevPost, - seal: { - ...prevPost.seal, - replies: newReplies, - }, - }; - - return updatedPost; - }; - - await updatePostInCache(variables, updater); - }, - }); -} - export function useDeleteReplyMutation() { const mutationFn = async (variables: { nest: Nest; @@ -2532,7 +2483,7 @@ export function useChannelSearch(nest: string, query: string) { ) .map((scItem: ChannelScanItem) => 'post' in scItem - ? ([bigInt(scItem.post.seal.id), scItem.post] as PostTuple) + ? ([bigInt(scItem.post.seal.id), scItem.post] as PageTuple) : ([ bigInt(scItem.reply.reply.seal.id), scItem.reply.reply, @@ -2627,110 +2578,3 @@ export function usePostToggler(postId: string) { isHidden, }; } - -export function useMyLastMessage( - whom: string, - postId?: string -): Post | Writ | Reply | null { - const isDmOrMultiDm = useIsDmOrMultiDm(whom); - let nest = ''; - - if (whom === '') { - return null; - } - - if (!isDmOrMultiDm) { - nest = `chat/${whom}`; - } - - const lastMessage = (pages: PagedPosts[] | PagedWrits[]) => { - if (!pages || pages.length === 0) { - return null; - } - - if ('writs' in pages[0]) { - // @ts-expect-error we already have a type guard - const writs = newWritTupleArray({ pages }); - const myWrits = writs.filter( - ([_id, msg]) => msg?.essay.author === window.our - ); - const lastWrit = last(myWrits); - if (!lastWrit) { - return null; - } - - return lastWrit[1]; - } - - if ('posts' in pages[0]) { - // @ts-expect-error we already have a type guard - const posts = newPostTupleArray({ pages }); - const myPosts = posts.filter( - ([_id, msg]) => msg?.essay.author === window.our - ); - const lastPost = last(myPosts); - if (!lastPost) { - return null; - } - - return lastPost[1]; - } - return null; - }; - - const lastReply = (replies: Replies) => { - const myReplies = Object.entries(replies).filter( - ([_id, msg]) => msg?.memo.author === window.our - ); - - const lastReplyMessage = last(myReplies); - if (!lastReplyMessage) { - return null; - } - - return lastReplyMessage[1]; - }; - - if (!isDmOrMultiDm) { - if (postId) { - const data = queryClient.getQueryData( - postKey(nest, postId) - ); - if (data && 'seal' in data) { - const { seal } = data; - return lastReply(seal.replies); - } - } - - const data = queryClient.getQueryData<{ pages: PagedPosts[] }>( - infinitePostsKey(nest) - ); - if (data) { - const { pages } = data; - return lastMessage(pages); - } - - return null; - } - - const data = queryClient.getQueryData<{ pages: PagedWrits[] }>( - ChatQueryKeys.infiniteDmsKey(whom) - ); - - if (data) { - const { pages } = data; - - return lastMessage(pages); - } - - return null; -} - -export function useIsEdited(message: Post | Writ | Reply) { - const isEdited = useMemo( - () => 'revision' in message && message.revision !== '0', - [message] - ); - - return isEdited; -} diff --git a/apps/tlon-web/src/state/channel/keys.ts b/apps/tlon-web/src/state/channel/keys.ts index 4f18ee973d..d03d0773c2 100644 --- a/apps/tlon-web/src/state/channel/keys.ts +++ b/apps/tlon-web/src/state/channel/keys.ts @@ -17,7 +17,6 @@ export const postKey = (nest: string, id: string) => { export const ChannnelKeys = { channel: channelKey, infinitePostsKey, - postKey, }; export default ChannnelKeys; diff --git a/apps/tlon-web/src/state/chat/chat.ts b/apps/tlon-web/src/state/chat/chat.ts index 0ad305d155..e713545b27 100644 --- a/apps/tlon-web/src/state/chat/chat.ts +++ b/apps/tlon-web/src/state/chat/chat.ts @@ -31,7 +31,6 @@ import { WritSeal, WritTuple, Writs, - newWritTupleArray, } from '@tloncorp/shared/dist/urbit/dms'; import { GroupMeta } from '@tloncorp/shared/dist/urbit/groups'; import { decToUd, udToDec } from '@urbit/api'; @@ -54,7 +53,6 @@ import useReactQuerySubscription from '@/logic/useReactQuerySubscription'; import { whomIsDm } from '@/logic/utils'; import queryClient from '@/queryClient'; -// eslint-disable-next-line import/no-cycle import { CacheId, PostStatus, TrackedPost } from '../channel/channel'; import ChatKeys from './keys'; import emptyMultiDm, { @@ -1347,7 +1345,22 @@ export function useInfiniteDMs(whom: string, initialTime?: string) { retry: false, }); - const writs = newWritTupleArray(data); + const writs: WritTuple[] = useMemo( + () => + _.uniqBy( + data?.pages + ?.map((page) => { + const writPages = Object.entries(page.writs).map( + ([k, v]) => [bigInt(udToDec(k)), v] as WritTuple + ); + return writPages; + }) + .flat() || [], + ([k]) => k.toString() + ).sort(([a], [b]) => a.compare(b)), + + [data] + ); return { data, diff --git a/apps/tlon-web/tsconfig.json b/apps/tlon-web/tsconfig.json index d696815a45..15504877c6 100644 --- a/apps/tlon-web/tsconfig.json +++ b/apps/tlon-web/tsconfig.json @@ -5,7 +5,6 @@ "allowJs": false, "skipLibCheck": true, "esModuleInterop": false, - "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -19,7 +18,7 @@ "vite/client", "vitest/globals", "@testing-library/jest-dom", - "vite-plugin-pwa/react" + "vite-plugin-pwa/client" ], "paths": { "@/*": ["./src/*"] @@ -31,7 +30,6 @@ "./*.config.ts", "./*.setup.ts", "./rube", - "cosmos/setup.ts", - "vite.config.mts" + "cosmos/setup.ts" ] } diff --git a/apps/tlon-web/vite.config.mts b/apps/tlon-web/vite.config.ts similarity index 78% rename from apps/tlon-web/vite.config.mts rename to apps/tlon-web/vite.config.ts index 9f32d5868c..4db124c691 100644 --- a/apps/tlon-web/vite.config.mts +++ b/apps/tlon-web/vite.config.ts @@ -1,17 +1,11 @@ -/// +/// z import { urbitPlugin } from '@urbit/vite-plugin-urbit'; import basicSsl from '@vitejs/plugin-basic-ssl'; import react from '@vitejs/plugin-react'; import analyze from 'rollup-plugin-analyzer'; import { visualizer } from 'rollup-plugin-visualizer'; import { fileURLToPath } from 'url'; -import { - BuildOptions, - Plugin, - PluginOption, - defineConfig, - loadEnv, -} from 'vite'; +import { BuildOptions, defineConfig, loadEnv } from 'vite'; import { VitePWA } from 'vite-plugin-pwa'; import packageJson from './package.json'; @@ -44,27 +38,27 @@ export default ({ mode }: { mode: string }) => { }; // eslint-disable-next-line - const plugins = (mode: string): PluginOption[] => { + const plugins = (mode: string) => { if (mode === 'mock' || mode === 'staging') { return [ - basicSsl() as Plugin, + basicSsl(), react({ jsxImportSource: '@welldone-software/why-did-you-render', - }) as PluginOption[], + }), ]; } return [ - process.env.SSL === 'true' ? (basicSsl() as PluginOption) : null, + process.env.SSL === 'true' ? basicSsl() : null, urbitPlugin({ base: 'groups', target: mode === 'dev2' ? SHIP_URL2 : SHIP_URL, changeOrigin: true, secure: false, - }) as PluginOption[], + }), react({ jsxImportSource: '@welldone-software/why-did-you-render', - }) as PluginOption[], + }), VitePWA({ base: '/apps/groups/', manifest, @@ -91,35 +85,34 @@ export default ({ mode }: { mode: string }) => { ? ['virtual:pwa-register/react'] : [], output: { - hashCharacters: 'base36' as any, manualChunks: { lodash: ['lodash'], 'lodash/fp': ['lodash/fp'], - 'urbit/api': ['@urbit/api'], - 'urbit/http-api': ['@urbit/http-api'], - 'urbit/sigil-js': ['@urbit/sigil-js'], + '@urbit/api': ['@urbit/api'], + '@urbit/http-api': ['@urbit/http-api'], + '@urbit/sigil-js': ['@urbit/sigil-js'], 'any-ascii': ['any-ascii'], 'react-beautiful-dnd': ['react-beautiful-dnd'], 'emoji-mart': ['emoji-mart'], - 'tiptap/core': ['@tiptap/core'], - 'tiptap/extension-placeholder': ['@tiptap/extension-placeholder'], - 'tiptap/extension-link': ['@tiptap/extension-link'], + '@tiptap/core': ['@tiptap/core'], + '@tiptap/extension-placeholder': ['@tiptap/extension-placeholder'], + '@tiptap/extension-link': ['@tiptap/extension-link'], 'react-virtuoso': ['react-virtuoso'], 'react-select': ['react-select'], 'react-hook-form': ['react-hook-form'], 'framer-motion': ['framer-motion'], 'date-fns': ['date-fns'], 'tippy.js': ['tippy.js'], - 'aws-sdk/client-s3': ['@aws-sdk/client-s3'], - 'aws-sdk/s3-request-presigner': ['@aws-sdk/s3-request-presigner'], + '@aws-sdk/client-s3': ['@aws-sdk/client-s3'], + '@aws-sdk/s3-request-presigner': ['@aws-sdk/s3-request-presigner'], refractor: ['refractor'], 'urbit-ob': ['urbit-ob'], 'hast-to-hyperscript': ['hast-to-hyperscript'], - 'radix-ui/react-dialog': ['@radix-ui/react-dialog'], - 'radix-ui/react-dropdown-menu': ['@radix-ui/react-dropdown-menu'], - 'radix-ui/react-popover': ['@radix-ui/react-popover'], - 'radix-ui/react-toast': ['@radix-ui/react-toast'], - 'radix-ui/react-tooltip': ['@radix-ui/react-tooltip'], + '@radix-ui/react-dialog': ['@radix-ui/react-dialog'], + '@radix-ui/react-dropdown-menu': ['@radix-ui/react-dropdown-menu'], + '@radix-ui/react-popover': ['@radix-ui/react-popover'], + '@radix-ui/react-toast': ['@radix-ui/react-toast'], + '@radix-ui/react-tooltip': ['@radix-ui/react-tooltip'], }, }, }; @@ -127,6 +120,7 @@ export default ({ mode }: { mode: string }) => { return defineConfig({ base: base(mode), server: { + https: process.env.SSL === 'true' ? true : false, host: 'localhost', port: process.env.E2E_PORT_3001 === 'true' ? 3001 : 3000, //NOTE the proxy used by vite is written poorly, and ends up removing @@ -142,9 +136,9 @@ export default ({ mode }: { mode: string }) => { proxy.on('proxyReq', (proxyReq) => { proxyReq.path = proxyReq.path.replaceAll('/@@@/', '//'); }); - }, - }, - }, + } + } + } }, build: mode !== 'profile' diff --git a/desk/app/channels.hoon b/desk/app/channels.hoon index d9d5688e04..28db75109e 100644 --- a/desk/app/channels.hoon +++ b/desk/app/channels.hoon @@ -423,19 +423,17 @@ %hide (~(put in hidden-posts) id-post.toggle) %show (~(del in hidden-posts) id-post.toggle) == - (give %fact ~[/ /v0 /v1] toggle-post+!>(toggle)) + (give %fact ~[/] toggle-post+!>(toggle)) :: :: ++ watch |= =(pole knot) ^+ cor - =? pole !?=([?(%v0 %v1) *] pole) - [%v0 pole] - ?+ pole ~|(bad-watch-path+`path`pole !!) - [?(%v0 %v1) ~] ?>(from-self cor) - [?(%v0 %v1) %unreads ~] ?>(from-self cor) - [?(%v0 %v1) =kind:c ship=@ name=@ ~] ?>(from-self cor) - [?(%v0 %v1) %said =kind:c host=@ name=@ %post time=@ reply=?(~ [@ ~])] + ?+ pole ~|(bad-watch-path+pole !!) + ~ ?>(from-self cor) + [%unreads ~] ?>(from-self cor) + [=kind:c ship=@ name=@ ~] ?>(from-self cor) + [%said =kind:c host=@ name=@ %post time=@ reply=?(~ [@ ~])] =/ host=ship (slav %p host.pole) =/ =nest:c [kind.pole host name.pole] =/ =plan:c =,(pole [(slav %ud time) ?~(reply ~ `(slav %ud -.reply))]) @@ -445,21 +443,21 @@ ++ watch-said |= [=nest:c =plan:c] ?. (~(has by v-channels) nest) - =/ =path (said-path nest plan) - ((safe-watch path [ship.nest server] path) |) + =/ wire (said-wire nest plan) + ((safe-watch wire [ship.nest server] wire) |) ::TODO not guaranteed to resolve, we might have partial backlog ca-abet:(ca-said:(ca-abed:ca-core nest) plan) :: -++ said-path +++ said-wire |= [=nest:c =plan:c] - ^- path + ^- wire %+ welp /said/[kind.nest]/(scot %p ship.nest)/[name.nest]/post/(scot %ud p.plan) ?~(q.plan / /(scot %ud u.q.plan)) :: ++ take-said |= [=nest:c =plan:c =sign:agent:gall] - =/ =path (said-path nest plan) + =/ =wire (said-wire nest plan) ^+ cor ?+ -.sign !! %watch-ack @@ -470,11 +468,11 @@ %kick ?: (~(has by voc) nest plan) cor :: subscription ended politely - (give %kick ~[path v0+path v1+path] ~) + (give %kick ~[wire] ~) :: %fact - =. cor (give %fact ~[path v0+path v1+path] cage.sign) - =. cor (give %kick ~[path v0+path v1+path] ~) + =. cor (give %fact ~[wire] cage.sign) + =. cor (give %kick ~[wire] ~) ?+ p.cage.sign ~|(funny-mark+p.cage.sign !!) %channel-denied cor(voc (~(put by voc) [nest plan] ~)) %channel-said @@ -566,19 +564,16 @@ ++ peek |= =(pole knot) ^- (unit (unit cage)) - ?> ?=(^ pole) - =? +.pole !?=([?(%v0 %v1) *] +.pole) - [%v0 +.pole] ?+ pole [~ ~] - [%x ?(%v0 %v1) %channels ~] ``channels+!>((uv-channels:utils v-channels)) - [%x ?(%v0 %v1) %init ~] ``noun+!>([unreads (uv-channels:utils v-channels)]) - [%x ?(%v0 %v1) %hidden-posts ~] ``hidden-posts+!>(hidden-posts) - [%x ?(%v0 %v1) %unreads ~] ``channel-unreads+!>(unreads) - [%x v=?(%v0 %v1) =kind:c ship=@ name=@ rest=*] + [%x %channels ~] ``channels+!>((uv-channels:utils v-channels)) + [%x %init ~] ``noun+!>([unreads (uv-channels:utils v-channels)]) + [%x %hidden-posts ~] ``hidden-posts+!>(hidden-posts) + [%x %unreads ~] ``channel-unreads+!>(unreads) + [%x =kind:c ship=@ name=@ rest=*] =/ =ship (slav %p ship.pole) - (ca-peek:(ca-abed:ca-core kind.pole ship name.pole) rest.pole v.pole) + (ca-peek:(ca-abed:ca-core kind.pole ship name.pole) rest.pole) :: - [%u ?(%v0 %v1) =kind:c ship=@ name=@ ~] + [%u =kind:c ship=@ name=@ ~] =/ =ship (slav %p ship.pole) ``loob+!>((~(has by v-channels) kind.pole ship name.pole)) == @@ -603,15 +598,10 @@ [nest ca-unread:(ca-abed:ca-core nest)] :: ++ pass-hark - |= =cage - ^- card - =/ =wire /hark - =/ =dock [our.bowl %hark] - [%pass wire %agent dock %poke cage] -++ pass-yarn |= =new-yarn:ha ^- card - (pass-hark hark-action-1+!>([%new-yarn new-yarn])) + =/ =cage hark-action-1+!>([%new-yarn new-yarn]) + [%pass /hark %agent [our.bowl %hark] %poke cage] :: ++ from-self =(our src):bowl :: @@ -641,7 +631,7 @@ ++ ca-area `path`/[kind.nest]/(scot %p ship.nest)/[name.nest] ++ ca-sub-wire (weld ca-area /updates) ++ ca-give-unread - (give %fact ~[/unreads /v0/unreads /v1/unreads] channel-unread-update+!>([nest ca-unread])) + (give %fact ~[/unreads] channel-unread-update+!>([nest ca-unread])) :: :: :: handle creating a channel @@ -689,28 +679,6 @@ ++ ca-a-remark |= =a-remark:c ^+ ca-core - =? ca-core =(%read -.a-remark) - %- emil - =/ last-read last-read.remark.channel - =+ .^(=carpet:ha %gx /(scot %p our.bowl)/hark/(scot %da now.bowl)/desk/groups/latest/noun) - %+ murn - ~(tap by cable.carpet) - |= [=rope:ha =thread:ha] - ^- (unit card) - ?~ can.rope ~ - ?. =(nest u.can.rope) ~ - =/ thread=(pole knot) ted.rope - =/ top-id=(unit id-post:c) - ?+ thread ~ - [* * * * id=@ rest=*] `(slav %ui (cat 3 '0i' id.thread)) - == - :: look at what post id the notification is coming from, and - :: if it's newer than the last read, mark the notification - :: read as well - ?~ top-id ~ - ?: (lth u.top-id last-read.remark.channel) ~ - =/ =cage hark-action-1+!>([%saw-rope rope]) - `(pass-hark cage) =. remark.channel ?- -.a-remark %watch remark.channel(watching &) @@ -1177,12 +1145,12 @@ ca-core =/ cs=(list content:ha) ~[[%ship author.post] ' mentioned you: ' (flatten:utils content.post)] - (emit (pass-yarn (ca-spin rope cs ~))) + (emit (pass-hark (ca-spin rope cs ~))) :: ?: (want-hark %any) =/ cs=(list content:ha) ~[[%ship author.post] ' sent a message: ' (flatten:utils content.post)] - (emit (pass-yarn (ca-spin rope cs ~))) + (emit (pass-hark (ca-spin rope cs ~))) ca-core :: ++ on-reply @@ -1215,7 +1183,7 @@ =; cs=(unit (list content:ha)) ?~ cs ca-core =/ =rope:ha (ca-rope -.kind-data.post id-post `id.reply) - (emit (pass-yarn (ca-spin rope u.cs ~))) + (emit (pass-hark (ca-spin rope u.cs ~))) :: notify because we wrote the post the reply responds to :: ?: =(author.post our.bowl) @@ -1313,21 +1281,7 @@ ++ ca-response |= =r-channel:c =/ =r-channels:c [nest r-channel] - ::TODO the mark type changing will give us trouble, right? - =. ca-core (give %fact ~[/v1 v1+ca-area] channel-response-1+!>(r-channels)) - =; r-simple=r-channels-simple-post:c - (give %fact ~[/ ca-area /v0 v0+ca-area] channel-response+!>(r-simple)) - :- nest - ?+ r-channel r-channel - [%posts *] - r-channel(posts (s-posts:utils posts.r-channel)) - :: - [%post * %set *] - r-channel(post.r-post (bind post.r-post.r-channel s-post:utils)) - :: - [%post * %reply * * %set *] - r-channel(reply.r-reply.r-post (bind reply.r-reply.r-post.r-channel s-reply:utils)) - == + (give %fact ~[/ ca-area] channel-response+!>(r-channels)) :: :: produce an up-to-date unread state :: @@ -1373,12 +1327,10 @@ :: handle scries :: ++ ca-peek - |= [=(pole knot) ver=?(%v0 %v1)] + |= =(pole knot) ^- (unit (unit cage)) ?+ pole [~ ~] - [%posts rest=*] - ?: =(ver %v0) (ca-peek-posts-0 rest.pole) - (ca-peek-posts-1 rest.pole) + [%posts rest=*] (ca-peek-posts rest.pole) [%perm ~] ``channel-perm+!>(perm.perm.channel) [%hark %rope post=@ ~] =/ id (slav %ud post.pole) @@ -1427,29 +1379,7 @@ (slav %p nedl.pole) == :: - ++ give-posts-0 - |= [mode=?(%outline %post) ls=(list [time (unit v-post:c)])] - ^- (unit (unit cage)) - =/ posts=v-posts:c (gas:on-v-posts:c *v-posts:c ls) - =; paged-posts=paged-simple-posts:c - ``channel-simple-posts+!>(paged-posts) - ?: =(0 (lent ls)) [*simple-posts:c ~ ~ 0] - =/ posts=simple-posts:c - ?: =(%post mode) (suv-posts:utils posts) - (suv-posts-without-replies:utils posts) - =/ newer=(unit time) - =/ more (tab:on-v-posts:c posts.channel `-:(rear ls) 1) - ?~(more ~ `-:(head more)) - =/ older=(unit time) - =/ more (bat:mo-v-posts:c posts.channel `-:(head ls) 1) - ?~(more ~ `-:(head more)) - :* posts - newer - older - (wyt:on-v-posts:c posts.channel) - == - :: - ++ give-posts-1 + ++ give-posts |= [mode=?(%outline %post) ls=(list [time (unit v-post:c)])] ^- (unit (unit cage)) =/ posts=v-posts:c (gas:on-v-posts:c *v-posts:c ls) @@ -1471,7 +1401,7 @@ (wyt:on-v-posts:c posts.channel) == :: - ++ ca-peek-posts-0 + ++ ca-peek-posts |= =(pole knot) ^- (unit (unit cage)) =* on on-v-posts:c @@ -1479,19 +1409,19 @@ [%newest count=@ mode=?(%outline %post) ~] =/ count (slav %ud count.pole) =/ ls (top:mo-v-posts:c posts.channel count) - (give-posts-0 mode.pole ls) + (give-posts mode.pole ls) :: [%older start=@ count=@ mode=?(%outline %post) ~] =/ count (slav %ud count.pole) =/ start (slav %ud start.pole) =/ ls (bat:mo-v-posts:c posts.channel `start count) - (give-posts-0 mode.pole ls) + (give-posts mode.pole ls) :: [%newer start=@ count=@ mode=?(%outline %post) ~] =/ count (slav %ud count.pole) =/ start (slav %ud start.pole) =/ ls (tab:on posts.channel `start count) - (give-posts-0 mode.pole ls) + (give-posts mode.pole ls) :: [%around time=@ count=@ mode=?(%outline %post) ~] =/ count (slav %ud count.pole) @@ -1502,55 +1432,7 @@ =/ posts ?~ post (welp older newer) (welp (snoc older [time u.post]) newer) - (give-posts-0 mode.pole posts) - :: - [%post time=@ ~] - =/ time (slav %ud time.pole) - =/ post (get:on posts.channel time) - ?~ post ~ - ?~ u.post `~ - ``channel-simple-post+!>((suv-post:utils u.u.post)) - :: - [%post %id time=@ %replies rest=*] - =/ time (slav %ud time.pole) - =/ post (get:on posts.channel `@da`time) - ?~ post ~ - ?~ u.post `~ - (ca-peek-replies-0 id.u.u.post replies.u.u.post rest.pole) - == - :: - ++ ca-peek-posts-1 - |= =(pole knot) - ^- (unit (unit cage)) - =* on on-v-posts:c - ?+ pole [~ ~] - [%newest count=@ mode=?(%outline %post) ~] - =/ count (slav %ud count.pole) - =/ ls (top:mo-v-posts:c posts.channel count) - (give-posts-1 mode.pole ls) - :: - [%older start=@ count=@ mode=?(%outline %post) ~] - =/ count (slav %ud count.pole) - =/ start (slav %ud start.pole) - =/ ls (bat:mo-v-posts:c posts.channel `start count) - (give-posts-1 mode.pole ls) - :: - [%newer start=@ count=@ mode=?(%outline %post) ~] - =/ count (slav %ud count.pole) - =/ start (slav %ud start.pole) - =/ ls (tab:on posts.channel `start count) - (give-posts-1 mode.pole ls) - :: - [%around time=@ count=@ mode=?(%outline %post) ~] - =/ count (slav %ud count.pole) - =/ time (slav %ud time.pole) - =/ older (bat:mo-v-posts:c posts.channel `time count) - =/ newer (tab:on posts.channel `time count) - =/ post (get:on posts.channel time) - =/ posts - ?~ post (welp older newer) - (welp (snoc older [time u.post]) newer) - (give-posts-1 mode.pole posts) + (give-posts mode.pole posts) :: [%post time=@ ~] =/ time (slav %ud time.pole) @@ -1564,64 +1446,28 @@ =/ post (get:on posts.channel `@da`time) ?~ post ~ ?~ u.post `~ - (ca-peek-replies-1 id.u.u.post replies.u.u.post rest.pole) - == - :: - ++ ca-peek-replies-0 - |= [parent-id=id-post:c replies=v-replies:c =(pole knot)] - ^- (unit (unit cage)) - =* on on-v-replies:c - ?+ pole [~ ~] - [%all ~] - ``channel-simple-replies+!>((suv-replies:utils parent-id replies)) - [%newest count=@ ~] - =/ count (slav %ud count.pole) - =/ reply-map (gas:on *v-replies:c (top:mo-v-replies:c replies count)) - ``channel-simple-replies+!>((suv-replies:utils parent-id reply-map)) - :: - [%older start=@ count=@ ~] - =/ count (slav %ud count.pole) - =/ start (slav %ud start.pole) - =/ reply-map (gas:on *v-replies:c (bat:mo-v-replies:c replies `start count)) - ``channel-simple-replies+!>((suv-replies:utils parent-id reply-map)) - :: - [%newer start=@ count=@ ~] - =/ count (slav %ud count.pole) - =/ start (slav %ud start.pole) - =/ reply-map (gas:on *v-replies:c (tab:on replies `start count)) - ``channel-simple-replies+!>((suv-replies:utils parent-id reply-map)) - :: - [%reply %id time=@ ~] - =/ time (slav %ud time.pole) - =/ reply (get:on-v-replies:c replies `@da`time) - ?~ reply ~ - ?~ u.reply `~ - ``channel-simple-reply+!>(`simple-reply:c`(suv-reply:utils parent-id u.u.reply)) + (ca-peek-replies id.u.u.post replies.u.u.post rest.pole) == :: - ++ ca-peek-replies-1 + ++ ca-peek-replies |= [parent-id=id-post:c replies=v-replies:c =(pole knot)] ^- (unit (unit cage)) =* on on-v-replies:c ?+ pole [~ ~] - [%all ~] - ``channel-replies+!>((uv-replies:utils parent-id replies)) + [%all ~] ``channel-replies+!>(replies) [%newest count=@ ~] =/ count (slav %ud count.pole) - =/ reply-map (gas:on *v-replies:c (top:mo-v-replies:c replies count)) - ``channel-replies+!>((uv-replies:utils parent-id reply-map)) + ``channel-replies+!>((gas:on *v-replies:c (top:mo-v-replies:c replies count))) :: [%older start=@ count=@ ~] =/ count (slav %ud count.pole) =/ start (slav %ud start.pole) - =/ reply-map (gas:on *v-replies:c (bat:mo-v-replies:c replies `start count)) - ``channel-replies+!>((uv-replies:utils parent-id reply-map)) + ``channel-replies+!>((gas:on *v-replies:c (bat:mo-v-replies:c replies `start count))) :: [%newer start=@ count=@ ~] =/ count (slav %ud count.pole) =/ start (slav %ud start.pole) - =/ reply-map (gas:on *v-replies:c (tab:on replies `start count)) - ``channel-replies+!>((uv-replies:utils parent-id reply-map)) + ``channel-replies+!>((gas:on *v-replies:c (tab:on replies `start count))) :: [%reply %id time=@ ~] =/ time (slav %ud time.pole) @@ -1683,7 +1529,7 @@ ?~ val.n.posts scan.s ?. (match u.val.n.posts match-type) scan.s :_ scan.s - [%post (suv-post-without-replies:utils u.val.n.posts)] + [%post (uv-post-without-replies:utils u.val.n.posts)] :: =. scan.s ?~ val.n.posts scan.s @@ -1697,7 +1543,7 @@ ?~ val.n.replies scan.s ?. (match-reply u.val.n.replies match-type) scan.s :_ scan.s - [%reply id-post (suv-reply:utils id-post u.val.n.replies)] + [%reply id-post (uv-reply:utils id-post u.val.n.replies)] :: $(replies l.replies) :: @@ -1725,7 +1571,7 @@ ?. (match u.val.n.posts match-type) s ?: (gth skip.s 0) s(skip (dec skip.s)) - =/ res [%post (suv-post-without-replies:utils u.val.n.posts)] + =/ res [%post (uv-post-without-replies:utils u.val.n.posts)] s(len (dec len.s), scan [res scan.s]) :: =. s @@ -1747,7 +1593,7 @@ ?. (match-reply u.val.n.replies match-type) s ?: (gth skip.s 0) s(skip (dec skip.s)) - =/ res [%reply id-post (suv-reply:utils id-post u.val.n.replies)] + =/ res [%reply id-post (uv-reply:utils id-post u.val.n.replies)] s(len (dec len.s), scan [res scan.s]) :: $(replies l.replies) diff --git a/desk/app/chat.hoon b/desk/app/chat.hoon index a8732fea44..8d8e1d32a6 100644 --- a/desk/app/chat.hoon +++ b/desk/app/chat.hoon @@ -712,15 +712,12 @@ == :: ++ pass-hark - |= =cage + |= =new-yarn:ha ^- card =/ =wire /hark =/ =dock [our.bowl %hark] - [%pass wire %agent dock %poke cage] -++ pass-yarn - |= =new-yarn:ha =/ =cage hark-action-1+!>([%new-yarn new-yarn]) - (pass-hark cage) + [%pass wire %agent dock %poke cage] :: ++ make-notice |= [=ship text=cord] @@ -1235,7 +1232,7 @@ == ~ =? cor (want-hark %to-us) - (emit (pass-yarn new-yarn)) + (emit (pass-hark new-yarn)) (cu-give-writs-diff diff.delta) :: %reply @@ -1266,7 +1263,7 @@ == ~ =? cor (want-hark %to-us) - (emit (pass-yarn new-yarn)) + (emit (pass-hark new-yarn)) (cu-give-writs-diff diff.delta) == == @@ -1309,15 +1306,6 @@ ++ cu-remark-diff |= diff=remark-diff:c ^+ cu-core - =? cor =(%read -.diff) - %- emil - =+ .^(=carpet:ha %gx /(scot %p our.bowl)/hark/(scot %da now.bowl)/desk/groups/latest/noun) - %+ murn - ~(tap by cable.carpet) - |= [=rope:ha =thread:ha] - ?. =(/club/(scot %uv id) ted.rope) ~ - =/ =cage hark-action-1+!>([%saw-rope rope]) - `(pass-hark cage) =. remark.club ?- -.diff %watch remark.club(watching &) @@ -1574,7 +1562,7 @@ == ~ =? cor (want-hark %to-us) - (emit (pass-yarn new-yarn)) + (emit (pass-hark new-yarn)) (di-give-writs-diff diff) :: %reply @@ -1604,7 +1592,7 @@ == ~ =? cor (want-hark %to-us) - (emit (pass-yarn new-yarn)) + (emit (pass-hark new-yarn)) (di-give-writs-diff diff) == == @@ -1773,15 +1761,6 @@ ++ di-remark-diff |= diff=remark-diff:c ^+ di-core - =? cor =(%read -.diff) - %- emil - =+ .^(=carpet:ha %gx /(scot %p our.bowl)/hark/(scot %da now.bowl)/desk/groups/latest/noun) - %+ murn - ~(tap by cable.carpet) - |= [=rope:ha =thread:ha] - ?. =(/dm/(scot %p ship) ted.rope) ~ - =/ =cage hark-action-1+!>([%saw-rope rope]) - `(pass-hark cage) =. remark.dm ?- -.diff %watch remark.dm(watching &) diff --git a/desk/app/notify.hoon b/desk/app/notify.hoon index bef2b27299..ea24b72507 100644 --- a/desk/app/notify.hoon +++ b/desk/app/notify.hoon @@ -1,5 +1,5 @@ :: -/- *notify, resource, ha=hark, c=channels +/- *notify, resource, ha=hark /+ default-agent, verb, dbug, agentio :: |% @@ -14,7 +14,6 @@ =whitelist == ++ clear-interval ~d7 -++ daily-stats-interval ~d1 :: +$ client-state $: providers=(jug @p term) @@ -25,23 +24,7 @@ =client-state == +$ base-state-2 -:: We set notifications to * because we had an issue with the notification type -:: definition being wrong and this wrong state made its way onto some ships. - $: notifications=* - base-state-0 - == -:: -+$ base-state-3 -:: We set notifications to * because we had an issue with the notification type -:: definition being wrong and this wrong state made its way onto some ships. - $: last-timer=time - notifications=* - base-state-0 - == -:: -+$ base-state-4 - $: last-timer=time - notifications=(map uid notification) + $: notifications=(map uid notification) base-state-0 == :: @@ -56,63 +39,16 @@ +$ state-3 [%3 base-state-2] :: -+$ state-4 - [%4 base-state-3] -:: -+$ state-5 - [%5 base-state-4] -:: +$ versioned-state $% state-0 state-1 state-2 state-3 - state-4 - state-5 - == -:: -+$ current-state state-5 -:: -++ migrate-state - |= old=versioned-state - ^- current-state - ?- -.old - %0 $(old (migrate-0-to-1 old)) - %1 $(old (migrate-1-to-2 old)) - %2 $(old (migrate-2-to-3 old)) - %3 $(old (migrate-3-to-4 old)) - %4 $(old (migrate-4-to-5 old)) - %5 old == :: -++ migrate-0-to-1 - |= old=state-0 - ^- state-1 - [%1 [+.old]] -:: -++ migrate-1-to-2 - |= old=state-1 - ^- state-2 - [%2 [~ +.old]] -:: -++ migrate-2-to-3 - |= old=state-2 - ^- state-3 - [%3 [+.old]] -:: -++ migrate-3-to-4 - |= old=state-3 - ^- state-4 - [%4 `@da`0 [+.old]] -:: -++ migrate-4-to-5 - |= old=state-4 - ^- state-5 - old(- %5, notifications ~) -:: -- :: -=| current-state +=| state-3 =* state - :: %- agent:dbug @@ -131,21 +67,20 @@ :_ this :~ (~(watch-our pass:io /hark) %hark /ui) (~(wait pass:io /clear) (add now.bowl clear-interval)) - [%pass / %agent [our.bowl %notify] %poke %provider-state-message !>(0)] == :: ++ on-save !>(state) ++ on-load |= =vase ^- (quip card _this) - =+ !<([old-state=versioned-state] vase) - =/ migrated (migrate-state old-state) - :_ this(state migrated) + =/ old=(unit state-3) + (mole |.(!<(state-3 vase))) + ?~ old + on-init + :_ this(state u.old) ?: (~(has by wex.bowl) [/hark our.bowl %hark]) - [%pass / %agent [our.bowl %notify] %poke %provider-state-message !>(0)]~ - :~ (~(watch-our pass:io /hark) %hark /ui) - [%pass / %agent [our.bowl %notify] %poke %provider-state-message !>(0)] - == + ~ + ~[(~(watch-our pass:io /hark) %hark /ui)] :: ++ on-poke |= [=mark =vase] @@ -153,7 +88,6 @@ |^ =^ cards state ?+ mark (on-poke:def mark vase) - %provider-state-message provider-state-message %notify-provider-action (handle-provider-action !<(provider-action vase)) %notify-client-action (handle-client-action !<(client-action vase)) == @@ -265,35 +199,6 @@ :_ state [(poke:pass [who.act %notify] %notify-provider-action !>(pact))]~ == - :: - ++ provider-state-message - ^- (quip card _state) - ~& "provider-state-message" - ?> =(our.bowl ~rivfur-livmet) - =/ now now.bowl - =/ time-since-last (sub `@`last-timer `@`now) - ~& ['time since last daily-stats-interval' time-since-last] - ?> (gth time-since-last daily-stats-interval) - ~& [provider-state] - =/ ps-list ~(tap by provider-state) - =/ total-providers (lent ps-list) - ~& ['total providers' total-providers] - =/ total-clients - %+ roll ps-list - |= [[provider=@tas =provider-entry] accumulator=@] - ^- @ - (add accumulator (lent ~(tap by clients.provider-entry))) - ~& ['total clients on all providers' total-clients] - =/ story=story:c [[%inline [[%bold ['BotPoast: ' ~]] 'Daily ' [%inline-code '%notify'] ' provider check-in. Total providers: ' [%bold [(scot %u total-providers) ~]] ', total clients: ' [%bold [(scot %u total-clients) ~]] '.' ~]]~] - =/ essay=essay:c [[story our.bowl now.bowl] [%chat ~]] - =/ nest=nest:c [%chat ~bitpyx-dildus %interface] - =/ channel-action=a-channels:c [%channel nest [%post [%add essay]]] - =/ new-timer (add now daily-stats-interval) - =. last-timer new-timer - :_ state - :~ [(poke:pass [our.bowl %channels] %channel-action !>(channel-action))] - [(~(wait pass:io /daily-timer) new-timer)] - == -- :: ++ on-watch @@ -322,8 +227,6 @@ =/ =uid (slav %uv uid.pole) =/ note=notification (~(got by notifications) uid) ``hark-note+!>(note) - [%x %provider-state ~] ``noun+!>(provider-state) - [%x %client-state ~] ``client-state+!>(client-state) == :: ++ on-agent @@ -380,11 +283,6 @@ |= [=wire =sign-arvo] ^- (quip card _this) ?+ wire (on-arvo:def wire sign-arvo) - [%daily-timer ~] - ?> ?=([%behn %wake *] sign-arvo) - :_ this - [%pass / %agent [our.bowl %notify] %poke %provider-state-message !>(0)]~ - :: [%register-binding @ @ @ ~] =/ who=@p (slav %p i.t.wire) =* service i.t.t.wire diff --git a/desk/app/profile.hoon b/desk/app/profile.hoon index b54813929c..4b2897580d 100644 --- a/desk/app/profile.hoon +++ b/desk/app/profile.hoon @@ -447,17 +447,6 @@ ++ on-load |= ole=vase ^- (quip card _this) - :: a different %profile agent has been spotted in the wild. it was not - :: compatible with the kelvin at which this agent was first released, made - :: no passes beyond eyre binding, and the software suite it was a part of - :: (realm) has since been discontinued. - :: so here we detect that case, and simply discard the previous state, - :: starting anew. - :: - ?: ?& =(%0 -.q.ole) - ?=(^ ((soft ,[%0 (map path mime) (set @p) (unit @t)]) q.ole)) - == - on-init =. state !<(state-0 ole) :: delay, so we don't end up scrying during load :: diff --git a/desk/app/profile/widgets.hoon b/desk/app/profile/widgets.hoon index acf5302af2..7b89d5b7c3 100644 --- a/desk/app/profile/widgets.hoon +++ b/desk/app/profile/widgets.hoon @@ -170,7 +170,7 @@ %+ join `manx`;br; %+ turn (to-wain:format bio.u.ours) |= p=@t ^- manx - [[%$ [%$ (trip p)] ~] ~] + [[%$ $+[p ~] ~] ~] == == == diff --git a/desk/lib/channel-json.hoon b/desk/lib/channel-json.hoon index 7265d5fc3f..f6c3fe8fb2 100644 --- a/desk/lib/channel-json.hoon +++ b/desk/lib/channel-json.hoon @@ -35,33 +35,6 @@ %unwatch ~ == :: - ++ r-channels-simple-post - |= [=nest:c =r-channel-simple-post:c] - %- pairs - :~ nest+(^nest nest) - response+(^r-channel-simple-post r-channel-simple-post) - == - :: - ++ r-channel-simple-post - |= r-channel=r-channel-simple-post:c - %+ frond -.r-channel - ?- -.r-channel - %posts (simple-posts posts.r-channel) - %post (pairs id+(id id.r-channel) r-post+(r-simple-post r-post.r-channel) ~) - %order (order order.r-channel) - %view s+view.r-channel - %sort s+sort.r-channel - %perm (perm perm.r-channel) - :: - %create (perm perm.r-channel) - %join (flag group.r-channel) - %leave ~ - %read ~ - %read-at s+(scot %ud time.r-channel) - %watch ~ - %unwatch ~ - == - :: ++ r-post |= =r-post:c %+ frond -.r-post @@ -78,22 +51,6 @@ == == :: - ++ r-simple-post - |= r-post=r-simple-post:c - %+ frond -.r-post - ?- -.r-post - %set ?~(post.r-post ~ (simple-post u.post.r-post)) - %reacts (reacts reacts.r-post) - %essay (essay essay.r-post) - :: - %reply - %- pairs - :~ id+(id id.r-post) - r-reply+(r-simple-reply r-reply.r-post) - meta+(reply-meta reply-meta.r-post) - == - == - :: ++ r-reply |= =r-reply:c %+ frond -.r-reply @@ -102,14 +59,6 @@ %reacts (reacts reacts.r-reply) == :: - ++ r-simple-reply - |= r-reply=r-simple-reply:c - %+ frond -.r-reply - ?- -.r-reply - %set ?~(reply.r-reply ~ (simple-reply u.reply.r-reply)) - %reacts (reacts reacts.r-reply) - == - :: ++ paged-posts |= pn=paged-posts:c %- pairs @@ -118,14 +67,6 @@ older+?~(older.pn ~ (id u.older.pn)) total+(numb total.pn) == - ++ paged-simple-posts - |= pn=paged-simple-posts:c - %- pairs - :~ posts+(simple-posts posts.pn) - newer+?~(newer.pn ~ (id u.newer.pn)) - older+?~(older.pn ~ (id u.older.pn)) - total+(numb total.pn) - == +| %rr :: ++ channels @@ -153,25 +94,9 @@ [(scot %ud id) ?~(post ~ (^post u.post))] :: ++ post - |= [=seal:c [rev=@ud =essay:c]] + |= [=seal:c =essay:c] %- pairs :~ seal+(^seal seal) - revision+s+(scot %ud rev) - essay+(^essay essay) - type+s+%post - == - :: - ++ simple-posts - |= posts=simple-posts:c - %- pairs - %+ turn (tap:on-simple-posts:c posts) - |= [id=id-post:c post=(unit simple-post:c)] - [(scot %ud id) ?~(post ~ (simple-post u.post))] - :: - ++ simple-post - |= [seal=simple-seal:c =essay:c] - %- pairs - :~ seal+(simple-seal seal) essay+(^essay essay) type+s+%post == @@ -183,22 +108,7 @@ |= [t=@da =reply:c] [(scot %ud t) (^reply reply)] :: - ++ simple-replies - |= replies=simple-replies:c - %- pairs - %+ turn (tap:on-simple-replies:c replies) - |= [t=@da reply=simple-reply:c] - [(scot %ud t) (simple-reply reply)] - :: ++ reply - |= [=reply-seal:c [rev=@ud =memo:c]] - %- pairs - :~ seal+(^reply-seal reply-seal) - revision+s+(scot %ud rev) - memo+(^memo memo) - == - :: - ++ simple-reply |= [=reply-seal:c =memo:c] %- pairs :~ seal+(^reply-seal reply-seal) @@ -214,15 +124,6 @@ meta+(reply-meta reply-meta.seal) == :: - ++ simple-seal - |= seal=simple-seal:c - %- pairs - :~ id+(id id.seal) - reacts+(reacts reacts.seal) - replies+(simple-replies replies.seal) - meta+(reply-meta reply-meta.seal) - == - :: ++ reply-seal |= =reply-seal:c %- pairs @@ -475,11 +376,11 @@ |= =reference:c %+ frond -.reference ?- -.reference - %post (simple-post post.reference) + %post (post post.reference) %reply %- pairs :~ id-post+(id id-post.reference) - reply+(simple-reply reply.reference) + reply+(reply reply.reference) == == :: @@ -539,7 +440,6 @@ %- of :~ add+memo del+id - edit+(ot id+id memo+memo ~) add-react+(ot id+id ship+ship react+so ~) del-react+(ot id+id ship+ship ~) == diff --git a/desk/lib/channel-utils.hoon b/desk/lib/channel-utils.hoon index 5e221050b9..6f96927b85 100644 --- a/desk/lib/channel-utils.hoon +++ b/desk/lib/channel-utils.hoon @@ -3,11 +3,7 @@ :: |% :: +uv-* functions convert posts, replies, and reacts into their "unversioned" -:: forms, suitable for responses to our subscribers. -:: +s-* functions convert those posts and replies into their "simple" forms, -:: suitable for responses to subscribers that use an older version of the api, -:: or just don't care about details like edit status. -:: +suv-* functions do both, sequentially. +:: forms, suitable for responses to our subscribers :: ++ uv-channels |= =v-channels:c @@ -31,45 +27,16 @@ ^- [id-post:c (unit post:c)] [id-post ?~(v-post ~ `(uv-post u.v-post))] :: -++ s-posts - |= =posts:c - ^- simple-posts:c - %+ gas:on-simple-posts:c *simple-posts:c - %+ turn (tap:on-posts:c posts) - |= [=id-post:c post=(unit post:c)] - ^- [id-post:c (unit simple-post:c)] - [id-post ?~(post ~ `(s-post u.post))] -:: -++ suv-posts - |= =v-posts:c - ^- simple-posts:c - %+ gas:on-simple-posts:c *simple-posts:c - %+ turn (tap:on-v-posts:c v-posts) - |= [=id-post:c v-post=(unit v-post:c)] - ^- [id-post:c (unit simple-post:c)] - [id-post ?~(v-post ~ `(suv-post u.v-post))] -:: ++ uv-post |= =v-post:c ^- post:c - :_ +.v-post + :_ +>.v-post :* id.v-post (uv-reacts reacts.v-post) (uv-replies id.v-post replies.v-post) (get-reply-meta v-post) == :: -++ s-post - |= =post:c - ^- simple-post:c - :_ +>.post - -.post(replies (s-replies replies.post)) -:: -++ suv-post - |= =v-post:c - ^- simple-post:c - (s-post (uv-post v-post)) -:: ++ uv-posts-without-replies |= =v-posts:c ^- posts:c @@ -79,31 +46,16 @@ ^- [id-post:c (unit post:c)] [id-post ?~(v-post ~ `(uv-post-without-replies u.v-post))] :: -++ suv-posts-without-replies - |= =v-posts:c - ^- simple-posts:c - %+ gas:on-simple-posts:c *simple-posts:c - %+ turn (tap:on-v-posts:c v-posts) - |= [=id-post:c v-post=(unit v-post:c)] - ^- [id-post:c (unit simple-post:c)] - [id-post ?~(v-post ~ `(suv-post-without-replies u.v-post))] -:: ++ uv-post-without-replies |= post=v-post:c ^- post:c - :_ +.post + :_ +>.post :* id.post (uv-reacts reacts.post) *replies:c (get-reply-meta post) == :: -++ suv-post-without-replies - |= post=v-post:c - ^- simple-post:c - =. replies.post ~ - (s-post (uv-post-without-replies post)) -:: ++ uv-replies |= [parent-id=id-post:c =v-replies:c] ^- replies:c @@ -111,40 +63,16 @@ %+ murn (tap:on-v-replies:c v-replies) |= [=time v-reply=(unit v-reply:c)] ^- (unit [id-reply:c reply:c]) - ?~ v-reply ~ ::REVIEW discrepance w/ +uv-posts? + ?~ v-reply ~ %- some [time (uv-reply parent-id u.v-reply)] :: -++ s-replies - |= =replies:c - ^- simple-replies:c - %+ gas:on-simple-replies:c *simple-replies:c - %+ turn (tap:on-replies:c replies) - |= [=time =reply:c] - ^- [id-reply:c simple-reply:c] - [time (s-reply reply)] -:: -++ suv-replies - |= [parent-id=id-post:c =v-replies:c] - ^- simple-replies:c - (s-replies (uv-replies parent-id v-replies)) -:: ++ uv-reply |= [parent-id=id-reply:c =v-reply:c] ^- reply:c - :_ +.v-reply + :_ +>.v-reply [id.v-reply parent-id (uv-reacts reacts.v-reply)] :: -++ s-reply - |= =reply:c - ^- simple-reply:c - [-.reply +>.reply] -:: -++ suv-reply - |= [parent-id=id-reply:c =v-reply:c] - ^- simple-reply:c - (s-reply (uv-reply parent-id v-reply)) -:: ++ uv-reacts |= =v-reacts:c ^- reacts:c @@ -159,27 +87,27 @@ ^- cage =/ post=(unit (unit v-post:c)) (get:on-v-posts:c posts p.plan) ?~ q.plan - =/ post=simple-post:c + =/ =post:c ?~ post ::TODO give "outline" that formally declares deletion - :- *simple-seal:c + :- *seal:c ?- kind.nest %diary [*memo:c %diary 'Unknown post' ''] %heap [*memo:c %heap ~ 'Unknown link'] %chat [[[%inline 'Unknown message' ~]~ ~nul *@da] %chat ~] == ?~ u.post - :- *simple-seal:c + :- *seal:c ?- kind.nest %diary [*memo:c %diary 'This post was deleted' ''] %heap [*memo:c %heap ~ 'This link was deleted'] %chat [[[%inline 'This message was deleted' ~]~ ~nul *@da] %chat ~] == - (suv-post-without-replies u.u.post) + (uv-post-without-replies u.u.post) [%channel-said !>(`said:c`[nest %post post])] :: - =/ reply=[reply-seal:c memo:c] + =/ =reply:c ?~ post [*reply-seal:c ~[%inline 'Comment on unknown post']~ ~nul *@da] ?~ u.post @@ -189,7 +117,7 @@ [*reply-seal:c ~[%inline 'Unknown comment']~ ~nul *@da] ?~ u.reply [*reply-seal:c ~[%inline 'This comment was deleted']~ ~nul *@da] - (suv-reply p.plan u.u.reply) + (uv-reply p.plan u.u.reply) [%channel-said !>(`said:c`[nest %reply p.plan reply])] :: ++ was-mentioned diff --git a/desk/lib/mark-warmer.hoon b/desk/lib/mark-warmer.hoon index 9807bcad42..398b794dfa 100644 --- a/desk/lib/mark-warmer.hoon +++ b/desk/lib/mark-warmer.hoon @@ -12,13 +12,9 @@ /$ scan %channel-scan %json /$ unreads %channel-unreads %json /$ posts %channel-posts %json -/$ simple-posts %channel-simple-posts %json /$ post %channel-post %json -/$ simple-post %channel-simple-post %json /$ replies %channel-replies %json -/$ simple-replies %channel-simple-replies %json /$ reply %channel-reply %json -/$ simple-reply %channel-simple-reply %json /$ hidden %hidden-posts %json /$ ch-unreads %chat-unreads %json /$ writ %writ %json diff --git a/desk/lib/test-agent.hoon b/desk/lib/test-agent.hoon index 1a0f0a8378..16e322e0fe 100644 --- a/desk/lib/test-agent.hoon +++ b/desk/lib/test-agent.hoon @@ -17,7 +17,6 @@ :: %- eval-mare :: =/ m (mare ,~) :: ^- form:m -:: ;< ~ bind:m (set-scry-gate |=(path `!>(%some-noun))) :: ;< caz=(list card) bind:m (do-init %my-agent-name my-agent-core) :: ;< ~ bind:m (ex-cards caz ~) :: ;< ~ bind:m (set-src ~dev) @@ -35,9 +34,8 @@ +$ agent $+(agent agent:gall) +$ bowl $+(bowl bowl:gall) +$ card $+(card card:agent:gall) -+$ scry $-(path (unit vase)) :: -+$ state [=agent =bowl =scry] :: passed continuously ++$ state [=agent =bowl] :: passed continuously ++ form-raw |$ [a] $-(state (output-raw a)) :: continuation ++ output-raw |$ [a] (each [out=a =state] tang) :: continue or fail :: @@ -126,93 +124,31 @@ ?<(ack [& path]) == :: -++ do :: execute agent lifecycle step with mocked scry +++ do |= call=$-(state [(list card) agent:gall]) =/ m (mare ,(list card)) ^- form:m |= s=state - =; result=(each [(list card) agent:gall] (list tank)) - ?: ?=(%| -.result) - |+p.result - =^ c agent.s p.result - =. bowl.s (play-cards bowl.s c) - &+[c s] - =; res=toon - ?- -.res - %0 :- %& - ::NOTE we would ;;, but it's too slow. - :: we know for a fact p.res is of the type we expect, - :: so we just play pretend with vases instead. - !< [(list card) agent:gall] - [-:!>(*[(list card) agent:gall]) p.res] - %1 |+~['blocking on scry' >;;(path p.res)<] - %2 |+p.res - == - %+ mock [. !=((call s))] - |= [ref=* pax=*] - ^- (unit (unit *)) - ?> ?=(^ ref) - ?> =(hoon-version -.ref) - =+ ;;(pax=path pax) - =/ res=(unit vase) (scry.s pax) - %. ?~(res ~ ``q.u.res) - :: warn about type mismatches if the tested code expects a result type - :: different from what the mocked scry produces. - :: - ::NOTE we would ;;, but it's too slow. - :: we can safely assume +.ref is indeed a type, - :: so we just play pretend with vases instead. - =+ !<(typ=type [-:!>(*type) +.ref]) - ?. &(?=(^ res) !(~(nest ut typ) | p.u.res)) - same - %- %*(. slog pri 2) - :~ 'mocked scry result mismatches expected type' - >pax< - (~(dunk ut typ) %need) - (~(dunk ut p.u.res) %have) - == -:: -++ jab-state - |= f=$-(state state) - =/ m (mare ,~) - ^- form:m - |= s=state - &+[~ (f s)] + =^ c agent.s (call s) + =. bowl.s (play-cards bowl.s c) + &+[c s] :: :: managed agent lifecycle :: ++ do-init - =+ scry-warn=& |= [dap=term =agent] =/ m (mare ,(list card)) ^- form:m - ;< old-scry=scry bind:m |=(s=state &+[scry.s s]) - ;< ~ bind:m %- set-scry-gate - |= p=path - ~? >> scry-warn - ['scrying during +on-init... careful!' p] - (old-scry p) - ;< b=bowl bind:m get-bowl - ;< ~ bind:m (set-bowl %*(. *bowl dap dap, our our.b, src our.b)) - ;< c=(list card) bind:m (do |=(s=state ~(on-init agent bowl.s))) - ;< ~ bind:m (set-scry-gate old-scry) - (pure:m c) + |= s=state + =. bowl.s %*(. *bowl dap dap, our our.bowl.s, src our.bowl.s) + =^ c agent.s ~(on-init agent bowl.s) + &+[c s] :: ++ do-load - =+ scry-warn=& |= =agent - =/ m (mare ,(list card)) - ^- form:m - ;< old-scry=scry bind:m |=(s=state &+[scry.s s]) - ;< ~ bind:m %- set-scry-gate - |= p=path - ~? >> scry-warn - ['scrying during +on-load... careful!' p] - (old-scry p) - ;< c=(list card) bind:m %- do |= s=state - (~(on-load agent bowl.s) ~(on-save agent.s bowl.s)) - ;< ~ bind:m (set-scry-gate old-scry) - (pure:m c) + %- do + |= s=state + (~(on-load agent bowl.s) ~(on-save agent.s bowl.s)) :: ++ do-poke |= [=mark =vase] @@ -319,13 +255,6 @@ %- jab-bowl |=(b=bowl b(now (add now.b d))) :: -++ set-scry-gate - |= f=scry - =/ m (mare ,~) - ^- form:m - |= s=state - &+[~ s(scry f)] -:: :: testing utilities :: ++ ex-equal @@ -336,13 +265,13 @@ =/ =tang (expect-eq:test expected actual) ?~(tang &+[~ s] |+tang) :: -++ ex-fail - |= form=(form-raw ,*) +++ ex-crash + |= form=$-(state *) =/ m (mare ,~) ^- form:m |= =state - =/ res (form state) - ?-(-.res %| &+[~ state], %& |+['expected failure, but succeeded']~) + =+ res=(mule |.((form state))) + ?-(-.res %| &+[~ state], %& |+['expected crash, but succeeded']~) :: ++ ex-cards |= [caz=(list card) exes=(list $-(card tang))] @@ -409,11 +338,4 @@ ++ ex-arvo |= [=wire note=note-arvo] (ex-card %pass wire %arvo note) -++ ex-scry-result - |= [=path =vase] - =/ m (mare ,~) - ^- form:m - ;< res=(unit (unit cage)) bind:m (get-peek path) - (ex-equal q:(need (need res)) vase) -:: -- diff --git a/desk/mar/channel/response-1.hoon b/desk/mar/channel/response-1.hoon deleted file mode 100644 index 4907feb57d..0000000000 --- a/desk/mar/channel/response-1.hoon +++ /dev/null @@ -1,15 +0,0 @@ -/- d=channels -/+ j=channel-json -|_ =r-channels:d -++ grad %noun -++ grow - |% - ++ noun r-channels - ++ json (r-channels:enjs:j r-channels) - -- -++ grab - |% - ++ noun r-channels:d - - -- --- diff --git a/desk/mar/channel/response.hoon b/desk/mar/channel/response.hoon index 225f0092ff..4907feb57d 100644 --- a/desk/mar/channel/response.hoon +++ b/desk/mar/channel/response.hoon @@ -1,15 +1,15 @@ /- d=channels /+ j=channel-json -|_ =r-channels-simple-post:d +|_ =r-channels:d ++ grad %noun ++ grow |% - ++ noun r-channels-simple-post - ++ json (r-channels-simple-post:enjs:j r-channels-simple-post) + ++ noun r-channels + ++ json (r-channels:enjs:j r-channels) -- ++ grab |% - ++ noun r-channels-simple-post:d + ++ noun r-channels:d -- -- diff --git a/desk/mar/channel/simple-post.hoon b/desk/mar/channel/simple-post.hoon deleted file mode 100644 index 6d7fdf4a6a..0000000000 --- a/desk/mar/channel/simple-post.hoon +++ /dev/null @@ -1,14 +0,0 @@ -/- c=channels -/+ j=channel-json -|_ =simple-post:c -++ grad %noun -++ grow - |% - ++ noun simple-post - ++ json (simple-post:enjs:j simple-post) - -- -++ grab - |% - ++ noun simple-post:c - -- --- diff --git a/desk/mar/channel/simple-posts.hoon b/desk/mar/channel/simple-posts.hoon deleted file mode 100644 index f7c69df38e..0000000000 --- a/desk/mar/channel/simple-posts.hoon +++ /dev/null @@ -1,14 +0,0 @@ -/- c=channels -/+ j=channel-json -|_ =paged-simple-posts:c -++ grad %noun -++ grow - |% - ++ noun paged-simple-posts - ++ json (paged-simple-posts:enjs:j paged-simple-posts) - -- -++ grab - |% - ++ noun paged-simple-posts:c - -- --- diff --git a/desk/mar/channel/simple-replies.hoon b/desk/mar/channel/simple-replies.hoon deleted file mode 100644 index dc5d2c219b..0000000000 --- a/desk/mar/channel/simple-replies.hoon +++ /dev/null @@ -1,14 +0,0 @@ -/- d=channels -/+ j=channel-json -|_ =simple-replies:d -++ grad %noun -++ grow - |% - ++ noun simple-replies - ++ json (simple-replies:enjs:j simple-replies) - -- -++ grab - |% - ++ noun simple-replies - -- --- diff --git a/desk/mar/channel/simple-reply.hoon b/desk/mar/channel/simple-reply.hoon deleted file mode 100644 index 9c9d32fae1..0000000000 --- a/desk/mar/channel/simple-reply.hoon +++ /dev/null @@ -1,14 +0,0 @@ -/- d=channels -/+ j=channel-json -|_ =simple-reply:d -++ grad %noun -++ grow - |% - ++ noun simple-reply - ++ json (simple-reply:enjs:j simple-reply) - -- -++ grab - |% - ++ noun simple-reply - -- --- diff --git a/desk/mar/channel/simple-response.hoon b/desk/mar/channel/simple-response.hoon deleted file mode 100644 index 225f0092ff..0000000000 --- a/desk/mar/channel/simple-response.hoon +++ /dev/null @@ -1,15 +0,0 @@ -/- d=channels -/+ j=channel-json -|_ =r-channels-simple-post:d -++ grad %noun -++ grow - |% - ++ noun r-channels-simple-post - ++ json (r-channels-simple-post:enjs:j r-channels-simple-post) - -- -++ grab - |% - ++ noun r-channels-simple-post:d - - -- --- diff --git a/desk/sur/channels.hoon b/desk/sur/channels.hoon index 3890f00b67..4166fddede 100644 --- a/desk/sur/channels.hoon +++ b/desk/sur/channels.hoon @@ -206,8 +206,8 @@ :: $scan: search results +$ scan (list reference) +$ reference - $% [%post post=simple-post] - [%reply =id-post reply=simple-reply] + $% [%post =post] + [%reply =id-post =reply] == :: $said: used for references +$ said (pair nest reference) @@ -426,24 +426,6 @@ $% [%set reply=(unit reply)] [%reacts =reacts] == -:: -+$ r-channels-simple-post [=nest =r-channel-simple-post] -+$ r-channel-simple-post - $% $<(?(%posts %post) r-channel) - [%posts posts=simple-posts] - [%post id=id-post r-post=r-simple-post] - == -:: -+$ r-simple-post - $% $<(?(%set %reply) r-post) - [%set post=(unit simple-post)] - [%reply id=id-reply =reply-meta r-reply=r-simple-reply] - == -:: -+$ r-simple-reply - $% $<(%set r-reply) - [%set reply=(unit simple-reply)] - == :: versions of backend types with their revision numbers stripped, :: because the frontend shouldn't care to learn those. :: @@ -469,36 +451,18 @@ older=(unit time) total=@ud == -+$ paged-simple-posts - $: posts=simple-posts - newer=(unit time) - older=(unit time) - total=@ud - == +$ posts ((mop id-post (unit post)) lte) -+$ simple-posts ((mop id-post (unit simple-post)) lte) -+$ post [seal [rev=@ud essay]] -+$ simple-post [simple-seal essay] ++$ post [seal essay] +$ seal $: id=id-post =reacts =replies =reply-meta == -+$ simple-seal - $: id=id-post - =reacts - replies=simple-replies - =reply-meta - == +$ reacts (map ship react) -+$ reply [reply-seal [rev=@ud memo]] -+$ simple-reply [reply-seal memo] ++$ reply [reply-seal memo] +$ replies ((mop id-reply reply) lte) -+$ simple-replies ((mop id-reply simple-reply) lte) +$ reply-seal [id=id-reply parent-id=id-post =reacts] ++ on-posts ((on id-post (unit post)) lte) -++ on-simple-posts ((on id-post (unit simple-post)) lte) ++ on-replies ((on id-reply reply) lte) -++ on-simple-replies ((on id-reply simple-reply) lte) -- diff --git a/desk/sur/notify.hoon b/desk/sur/notify.hoon index a16a1f3a96..4443e78925 100644 --- a/desk/sur/notify.hoon +++ b/desk/sur/notify.hoon @@ -1,13 +1,5 @@ /- resource |% -+$ provider-entry - $: notify-endpoint=@t - binding-endpoint=@t - auth-token=@t - clients=(map ship binding=(unit @t)) - =whitelist - == -+$ provider-state (map term provider-entry) +$ provider-action $% [%add service=term notify=@t binding=@t auth-token=@t =whitelist] [%remove service=term] @@ -30,7 +22,7 @@ +$ bin [=path =place] +$ place [=desk =path] +$ body [title=content =content =time binned=path link=path] - +$ content (list $%([%ship =ship] [%text =cord])) + +$ content (list $%([%ship =ship] [%cord =cord])) -- :: +$ whitelist diff --git a/desk/tests/app/chat.hoon b/desk/tests/app/chat.hoon index 7dcb5afcc0..5e78906c66 100644 --- a/desk/tests/app/chat.hoon +++ b/desk/tests/app/chat.hoon @@ -1,81 +1,48 @@ -/- c=chat, ch=channels, h=hark, contacts -/+ *test-agent +/- e=epic, c=chat +/+ *test /= agent /app/chat |% -++ dap %chat-test -++ test-dm-notification-clearing - %- eval-mare - =/ m (mare ,~) - ;< * bind:m (do-init dap agent) - ;< * bind:m (set-scry-gate scries) - ;< * bind:m (jab-bowl |=(b=bowl b(our ~dev, src ~zod))) - ;< bw=bowl bind:m get-bowl - =/ =verse:ch [%inline ~['hi ' [%ship ~dev]]] - =/ =diff:dm:c (dm-message ~zod now.bw verse) - :: start a dm from zod - :: ~& 'starting dm from zod' - ;< * bind:m (do-poke %chat-dm-diff !>(diff)) - ;< * bind:m (set-src ~dev) - :: accept the dm and set read - :: ~& 'accepting dm from zod' - ;< * bind:m (wait ~s1) - ;< bw=bowl bind:m get-bowl - ;< * bind:m (do-poke %chat-dm-rsvp !>([~zod &])) - ;< * bind:m (do-poke %chat-remark-action !>([[%ship ~zod] %read ~])) - :: send another dm from zod with a notification - ;< * bind:m (set-src ~zod) - :: ~& 'sending dm from zod with notification' - ;< * bind:m (wait ~s1) - ;< bw=bowl bind:m get-bowl - =/ =diff:dm:c (dm-message ~zod now.bw verse) - ;< caz=(list card) bind:m (do-poke %chat-dm-diff !>(diff)) - :: expect a notification and an unread dm - =/ =whom:c [%ship ~zod] - =/ =unread:unreads:c [now.bw 1 `[[[~zod now.bw] now.bw] 1] ~] - =/ =unreads:c (malt [whom unread] ~) - =/ =response:writs:c [[~zod now.bw] %add [~[verse] ~zod now.bw] now.bw] - =/ =new-yarn:h [& & rope content /dm/~zod ~] - ;< * bind:m (ex-scry-result /x/unreads !>(unreads)) - :: ~& 'have unreads' - ;< * bind:m - %+ ex-cards caz - :~ (ex-poke /contacts/~zod [~dev %contacts] act:mar:contacts !>([%heed ~[~zod]])) - (ex-fact ~[/unreads] %chat-unread-update !>([whom unread])) - (ex-poke /hark [~dev %hark] %hark-action-1 !>([%new-yarn new-yarn])) - (ex-fact ~[/dm/~zod] %writ-response !>(response)) - (ex-fact ~[/dm/~zod/writs] %writ-response !>(response)) - == - ;< * bind:m (wait ~s1) - ;< bw=bowl bind:m get-bowl - =/ =unread:unreads:c [(sub now.bw ~s1) 0 ~ ~] - :: ~& 'marked read and notification cleared' - ;< caz=(list card) bind:m (do-poke %chat-remark-action !>([[%ship ~zod] %read ~])) - %+ ex-cards caz - :~ (ex-poke /hark [~dev %hark] %hark-action-1 !>([%saw-rope rope])) - (ex-fact ~[/unreads] %chat-unread-update !>([whom unread])) ++$ current-state + $: %1 + chats=(map flag:c chat:c) + dms=(map ship dm:c) + clubs=(map id:club:c club:c) + drafts=(map whom:c story:c) + pins=(list whom:c) + bad=(set ship) + inv=(set ship) + voc=(map [flag:c id:c] (unit said:c)) == -::++ test-club-notification-clearing -++ scries - |= =path - ^- (unit vase) - ?+ path ~ - [%gu @ %groups @ *] `!>(%.y) - [%gx @ %groups @ %volume *] `!>(%soft) - [%gx @ %hark @ *] `!>(carpet) +++ chat-1 [~zod %test] +++ bowl + |= run=@ud + ^- bowl:gall + :* [~zod ~zod %chat] + [~ ~] + [run `@uvJ`(shax run) (add (mul run ~s1) *time) [~zod %groups ud+run]] == -++ rope `rope:h`[~ ~ %groups /dm/~zod] -++ content `(list content:h)`~[[%ship ~zod] ': ' 'hi ~dev'] -++ carpet - =/ =yarn:h [0v0 rope *time content /dm/~zod ~] - ^- carpet:h - :* [%desk %groups] - (malt ~[[0v0 yarn]]) - (malt ~[[rope (silt ~[0v0])]]) - 0 - == -++ dm-message - |= [author=ship =time =verse:ch] - ^- diff:dm:c - =/ =memo:ch [~[verse] author time] - [[author time] %add memo ~ ~] +-- +|% +++ zod `agent:gall`agent +++ wet `agent:gall`agent +++ test-epic + =| run=@ud + =| =create:c + =| =flag:c + =^ mov1 agent + (~(on-poke agent (bowl run)) %chat-create !>(create(name %test))) + =^ mova agent + =+ =< .(our ~wet) (bowl run) + (~(on-poke agent -) %flag !>(flag(q %test))) + =^ mov2 agent + =+ =< .(our ~wet) (bowl run) + (~(on-agent agent -) /epic [%fact %epic !>(`epic:e`0)]) + =. run +(run) + =+ !<([=current-state epic=@ud] on-save:agent) + ~& >> chats+chats.current-state + :: should be 0 + ~& >> epic+epic + :: should be a resub watch task as the epics match. + ~& >> mov2+mov2 + (expect-eq !>(&) !>(&)) -- diff --git a/desk/tests/lib/negotiate.hoon b/desk/tests/lib/negotiate.hoon index 4719ebee1c..6a121561c7 100644 --- a/desk/tests/lib/negotiate.hoon +++ b/desk/tests/lib/negotiate.hoon @@ -121,6 +121,13 @@ ;< state=vase bind:m get-save (pure:m o:!<([[%negotiate o=libstate] vase] state)) :: +++ ex-scry-result + |= [=path =vase] + =/ m (mare ,~) + ^- form:m + ;< res=(unit (unit cage)) bind:m (get-peek path) + (ex-equal q:(need (need res)) vase) +:: ++ perform-hear-version |= [=gill:gall =protocol:libn =version:libn] %+ do-agent @@ -321,7 +328,7 @@ :: if we know for sure we don't match, sending a poke must crash :: ;< * bind:m (perform-hear-version [~zod %hard] %prot %miss) - ;< ~ bind:m (ex-fail (do-poke %emit-cards !>([poke]~))) + ;< ~ bind:m (ex-crash (do-poke %emit-cards !>([poke]~))) :: once we do exactly match, poking is fine again :: ;< * bind:m (perform-hear-version [~zod %hard] %prot %vers) diff --git a/package-lock.json b/package-lock.json index 0edb547196..6b3a2e10be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,7 +37,6 @@ "@realm/react": "^0.6.2", "@tloncorp/shared": "*", "@urbit/aura": "^1.0.0", - "@urbit/http-api": "^3.1.0-dev-2", "classnames": "^2.3.2", "dotenv-expand": "^11.0.6", "expo": "^50.0.6", @@ -45,7 +44,6 @@ "expo-asset": "~9.0.2", "expo-clipboard": "~5.0.1", "expo-constants": "~15.4.5", - "expo-dev-client": "^3.3.9", "expo-device": "~5.9.3", "expo-file-system": "~16.0.6", "expo-localization": "~14.8.3", @@ -63,23 +61,17 @@ "react-native-branch": "^5.9.0", "react-native-country-codes-picker": "^2.3.3", "react-native-device-info": "^10.8.0", - "react-native-fetch-api": "^3.0.0", "react-native-gesture-handler": "~2.14.0", - "react-native-get-random-values": "^1.11.0", "react-native-phone-input": "^1.3.7", - "react-native-polyfill-globals": "^3.1.0", "react-native-reanimated": "~3.6.2", "react-native-safe-area-context": "4.8.2", "react-native-screens": "~3.29.0", - "react-native-sse": "^1.2.1", "react-native-storage": "^1.0.1", "react-native-svg": "^14.1.0", "react-native-url-polyfill": "^2.0.0", "react-native-webview": "13.6.4", "realm": "^12.6.0", - "tailwind-rn": "^4.2.0", - "text-encoding": "^0.7.0", - "web-streams-polyfill": "^3.3.3" + "tailwind-rn": "^4.2.0" }, "devDependencies": { "@react-native/metro-config": "^0.73.5", @@ -784,6 +776,7 @@ "@aws-sdk/s3-request-presigner": "^3.190.0", "@emoji-mart/data": "^1.0.6", "@emoji-mart/react": "^1.0.1", + "@faker-js/faker": "^6.3.1", "@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-collapsible": "^1.0.3", "@radix-ui/react-dialog": "^1.0.4", @@ -830,7 +823,7 @@ "@types/marked": "^4.3.0", "@urbit/api": "^2.2.0", "@urbit/aura": "^1.0.0", - "@urbit/http-api": "^3.1.0-dev-2", + "@urbit/http-api": "^3.0.0", "@urbit/sigil-js": "^2.1.0", "any-ascii": "^0.3.1", "big-integer": "^1.6.51", @@ -903,7 +896,6 @@ "zustand": "^3.7.2" }, "devDependencies": { - "@faker-js/faker": "^8.4.1", "@playwright/test": "^1.33.0", "@tailwindcss/aspect-ratio": "^0.4.0", "@tailwindcss/container-queries": "^0.1.0", @@ -933,9 +925,9 @@ "@typescript-eslint/eslint-plugin": "^5.19.0", "@typescript-eslint/parser": "^5.19.0", "@urbit/eslint-config": "^1.0.3", - "@urbit/vite-plugin-urbit": "^2.0.1", - "@vitejs/plugin-basic-ssl": "^1.1.0", - "@vitejs/plugin-react": "^4.2.1", + "@urbit/vite-plugin-urbit": "^0.8.0", + "@vitejs/plugin-basic-ssl": "^1.0.1", + "@vitejs/plugin-react": "^4.0.3", "@welldone-software/why-did-you-render": "^7.0.1", "autoprefixer": "^10.4.4", "concurrently": "^8.0.1", @@ -967,61 +959,11 @@ "tailwindcss": "^3.2.7", "tailwindcss-theme-swapper": "^0.7.3", "tar-fs": "^3.0.4", - "tsc-files": "^1.1.4", - "typescript": "^5.4.2", - "vite": "^5.1.6", - "vite-plugin-pwa": "^0.17.5", - "vitest": "^0.34.1", - "workbox-window": "^7.0.0" - } - }, - "apps/tlon-web/node_modules/@apideck/better-ajv-errors": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.6.tgz", - "integrity": "sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==", - "dev": true, - "dependencies": { - "json-schema": "^0.4.0", - "jsonpointer": "^5.0.0", - "leven": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "ajv": ">=8" - } - }, - "apps/tlon-web/node_modules/@esbuild/darwin-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", - "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "apps/tlon-web/node_modules/@faker-js/faker": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.4.1.tgz", - "integrity": "sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/fakerjs" - } - ], - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0", - "npm": ">=6.14.13" + "tsc-files": "^1.1.3", + "typescript": "^4.6.3", + "vite": "^4.4.3", + "vite-plugin-pwa": "^0.14.4", + "vitest": "^0.34.1" } }, "apps/tlon-web/node_modules/@jest/schemas": { @@ -1036,175 +978,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "apps/tlon-web/node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.0.tgz", - "integrity": "sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] - }, - "apps/tlon-web/node_modules/@rollup/rollup-android-arm64": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.0.tgz", - "integrity": "sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] - }, - "apps/tlon-web/node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.0.tgz", - "integrity": "sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "apps/tlon-web/node_modules/@rollup/rollup-darwin-x64": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.0.tgz", - "integrity": "sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "apps/tlon-web/node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.0.tgz", - "integrity": "sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "apps/tlon-web/node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.0.tgz", - "integrity": "sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "apps/tlon-web/node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.0.tgz", - "integrity": "sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "apps/tlon-web/node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.0.tgz", - "integrity": "sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "apps/tlon-web/node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.0.tgz", - "integrity": "sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "apps/tlon-web/node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.0.tgz", - "integrity": "sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "apps/tlon-web/node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.0.tgz", - "integrity": "sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "apps/tlon-web/node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.0.tgz", - "integrity": "sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "apps/tlon-web/node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.0.tgz", - "integrity": "sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, "apps/tlon-web/node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -1279,22 +1052,6 @@ "url": "https://opencollective.com/vitest" } }, - "apps/tlon-web/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "apps/tlon-web/node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", @@ -1316,64 +1073,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "apps/tlon-web/node_modules/esbuild": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", - "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.19.12", - "@esbuild/android-arm": "0.19.12", - "@esbuild/android-arm64": "0.19.12", - "@esbuild/android-x64": "0.19.12", - "@esbuild/darwin-arm64": "0.19.12", - "@esbuild/darwin-x64": "0.19.12", - "@esbuild/freebsd-arm64": "0.19.12", - "@esbuild/freebsd-x64": "0.19.12", - "@esbuild/linux-arm": "0.19.12", - "@esbuild/linux-arm64": "0.19.12", - "@esbuild/linux-ia32": "0.19.12", - "@esbuild/linux-loong64": "0.19.12", - "@esbuild/linux-mips64el": "0.19.12", - "@esbuild/linux-ppc64": "0.19.12", - "@esbuild/linux-riscv64": "0.19.12", - "@esbuild/linux-s390x": "0.19.12", - "@esbuild/linux-x64": "0.19.12", - "@esbuild/netbsd-x64": "0.19.12", - "@esbuild/openbsd-x64": "0.19.12", - "@esbuild/sunos-x64": "0.19.12", - "@esbuild/win32-arm64": "0.19.12", - "@esbuild/win32-ia32": "0.19.12", - "@esbuild/win32-x64": "0.19.12" - } - }, - "apps/tlon-web/node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "apps/tlon-web/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, "apps/tlon-web/node_modules/local-pkg": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz", @@ -1421,50 +1120,6 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, - "apps/tlon-web/node_modules/rollup": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.0.tgz", - "integrity": "sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==", - "dev": true, - "dependencies": { - "@types/estree": "1.0.5" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.13.0", - "@rollup/rollup-android-arm64": "4.13.0", - "@rollup/rollup-darwin-arm64": "4.13.0", - "@rollup/rollup-darwin-x64": "4.13.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.13.0", - "@rollup/rollup-linux-arm64-gnu": "4.13.0", - "@rollup/rollup-linux-arm64-musl": "4.13.0", - "@rollup/rollup-linux-riscv64-gnu": "4.13.0", - "@rollup/rollup-linux-x64-gnu": "4.13.0", - "@rollup/rollup-linux-x64-musl": "4.13.0", - "@rollup/rollup-win32-arm64-msvc": "4.13.0", - "@rollup/rollup-win32-ia32-msvc": "4.13.0", - "@rollup/rollup-win32-x64-msvc": "4.13.0", - "fsevents": "~2.3.2" - } - }, - "apps/tlon-web/node_modules/source-map": { - "version": "0.8.0-beta.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", - "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", - "dev": true, - "dependencies": { - "whatwg-url": "^7.0.0" - }, - "engines": { - "node": ">= 8" - } - }, "apps/tlon-web/node_modules/tinypool": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.7.0.tgz", @@ -1474,83 +1129,6 @@ "node": ">=14.0.0" } }, - "apps/tlon-web/node_modules/tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "apps/tlon-web/node_modules/typescript": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", - "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "apps/tlon-web/node_modules/vite": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.6.tgz", - "integrity": "sha512-yYIAZs9nVfRJ/AiOLCA91zzhjsHUgMjB+EigzFb6W2XTLO8JixBCKCjvhKZaye+NKYHCrkv3Oh50dH9EdLU2RA==", - "dev": true, - "dependencies": { - "esbuild": "^0.19.3", - "postcss": "^8.4.35", - "rollup": "^4.2.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, "apps/tlon-web/node_modules/vite-node": { "version": "0.34.6", "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.34.6.tgz", @@ -1574,30 +1152,6 @@ "url": "https://opencollective.com/vitest" } }, - "apps/tlon-web/node_modules/vite-plugin-pwa": { - "version": "0.17.5", - "resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.17.5.tgz", - "integrity": "sha512-UxRNPiJBzh4tqU/vc8G2TxmrUTzT6BqvSzhszLk62uKsf+npXdvLxGDz9C675f4BJi6MbD2tPnJhi5txlMzxbQ==", - "dev": true, - "dependencies": { - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "pretty-bytes": "^6.1.1", - "workbox-build": "^7.0.0", - "workbox-window": "^7.0.0" - }, - "engines": { - "node": ">=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "vite": "^3.1.0 || ^4.0.0 || ^5.0.0", - "workbox-build": "^7.0.0", - "workbox-window": "^7.0.0" - } - }, "apps/tlon-web/node_modules/vitest": { "version": "0.34.6", "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.34.6.tgz", @@ -1675,280 +1229,6 @@ } } }, - "apps/tlon-web/node_modules/webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true - }, - "apps/tlon-web/node_modules/whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "dev": true, - "dependencies": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, - "apps/tlon-web/node_modules/workbox-background-sync": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-7.0.0.tgz", - "integrity": "sha512-S+m1+84gjdueM+jIKZ+I0Lx0BDHkk5Nu6a3kTVxP4fdj3gKouRNmhO8H290ybnJTOPfBDtTMXSQA/QLTvr7PeA==", - "dev": true, - "dependencies": { - "idb": "^7.0.1", - "workbox-core": "7.0.0" - } - }, - "apps/tlon-web/node_modules/workbox-broadcast-update": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-7.0.0.tgz", - "integrity": "sha512-oUuh4jzZrLySOo0tC0WoKiSg90bVAcnE98uW7F8GFiSOXnhogfNDGZelPJa+6KpGBO5+Qelv04Hqx2UD+BJqNQ==", - "dev": true, - "dependencies": { - "workbox-core": "7.0.0" - } - }, - "apps/tlon-web/node_modules/workbox-build": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-7.0.0.tgz", - "integrity": "sha512-CttE7WCYW9sZC+nUYhQg3WzzGPr4IHmrPnjKiu3AMXsiNQKx+l4hHl63WTrnicLmKEKHScWDH8xsGBdrYgtBzg==", - "dev": true, - "dependencies": { - "@apideck/better-ajv-errors": "^0.3.1", - "@babel/core": "^7.11.1", - "@babel/preset-env": "^7.11.0", - "@babel/runtime": "^7.11.2", - "@rollup/plugin-babel": "^5.2.0", - "@rollup/plugin-node-resolve": "^11.2.1", - "@rollup/plugin-replace": "^2.4.1", - "@surma/rollup-plugin-off-main-thread": "^2.2.3", - "ajv": "^8.6.0", - "common-tags": "^1.8.0", - "fast-json-stable-stringify": "^2.1.0", - "fs-extra": "^9.0.1", - "glob": "^7.1.6", - "lodash": "^4.17.20", - "pretty-bytes": "^5.3.0", - "rollup": "^2.43.1", - "rollup-plugin-terser": "^7.0.0", - "source-map": "^0.8.0-beta.0", - "stringify-object": "^3.3.0", - "strip-comments": "^2.0.1", - "tempy": "^0.6.0", - "upath": "^1.2.0", - "workbox-background-sync": "7.0.0", - "workbox-broadcast-update": "7.0.0", - "workbox-cacheable-response": "7.0.0", - "workbox-core": "7.0.0", - "workbox-expiration": "7.0.0", - "workbox-google-analytics": "7.0.0", - "workbox-navigation-preload": "7.0.0", - "workbox-precaching": "7.0.0", - "workbox-range-requests": "7.0.0", - "workbox-recipes": "7.0.0", - "workbox-routing": "7.0.0", - "workbox-strategies": "7.0.0", - "workbox-streams": "7.0.0", - "workbox-sw": "7.0.0", - "workbox-window": "7.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "apps/tlon-web/node_modules/workbox-build/node_modules/@rollup/plugin-replace": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz", - "integrity": "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==", - "dev": true, - "dependencies": { - "@rollup/pluginutils": "^3.1.0", - "magic-string": "^0.25.7" - }, - "peerDependencies": { - "rollup": "^1.20.0 || ^2.0.0" - } - }, - "apps/tlon-web/node_modules/workbox-build/node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "apps/tlon-web/node_modules/workbox-build/node_modules/magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", - "dev": true, - "dependencies": { - "sourcemap-codec": "^1.4.8" - } - }, - "apps/tlon-web/node_modules/workbox-build/node_modules/pretty-bytes": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", - "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "apps/tlon-web/node_modules/workbox-build/node_modules/rollup": { - "version": "2.79.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", - "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", - "dev": true, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=10.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "apps/tlon-web/node_modules/workbox-build/node_modules/workbox-precaching": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-7.0.0.tgz", - "integrity": "sha512-EC0vol623LJqTJo1mkhD9DZmMP604vHqni3EohhQVwhJlTgyKyOkMrZNy5/QHfOby+39xqC01gv4LjOm4HSfnA==", - "dev": true, - "dependencies": { - "workbox-core": "7.0.0", - "workbox-routing": "7.0.0", - "workbox-strategies": "7.0.0" - } - }, - "apps/tlon-web/node_modules/workbox-cacheable-response": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-7.0.0.tgz", - "integrity": "sha512-0lrtyGHn/LH8kKAJVOQfSu3/80WDc9Ma8ng0p2i/5HuUndGttH+mGMSvOskjOdFImLs2XZIimErp7tSOPmu/6g==", - "dev": true, - "dependencies": { - "workbox-core": "7.0.0" - } - }, - "apps/tlon-web/node_modules/workbox-core": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-7.0.0.tgz", - "integrity": "sha512-81JkAAZtfVP8darBpfRTovHg8DGAVrKFgHpOArZbdFd78VqHr5Iw65f2guwjE2NlCFbPFDoez3D3/6ZvhI/rwQ==", - "dev": true - }, - "apps/tlon-web/node_modules/workbox-expiration": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-7.0.0.tgz", - "integrity": "sha512-MLK+fogW+pC3IWU9SFE+FRStvDVutwJMR5if1g7oBJx3qwmO69BNoJQVaMXq41R0gg3MzxVfwOGKx3i9P6sOLQ==", - "dev": true, - "dependencies": { - "idb": "^7.0.1", - "workbox-core": "7.0.0" - } - }, - "apps/tlon-web/node_modules/workbox-google-analytics": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-7.0.0.tgz", - "integrity": "sha512-MEYM1JTn/qiC3DbpvP2BVhyIH+dV/5BjHk756u9VbwuAhu0QHyKscTnisQuz21lfRpOwiS9z4XdqeVAKol0bzg==", - "deprecated": "It is not compatible with newer versions of GA starting with v4, as long as you are using GAv3 it should be ok, but the package is not longer being maintained", - "dev": true, - "dependencies": { - "workbox-background-sync": "7.0.0", - "workbox-core": "7.0.0", - "workbox-routing": "7.0.0", - "workbox-strategies": "7.0.0" - } - }, - "apps/tlon-web/node_modules/workbox-navigation-preload": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-7.0.0.tgz", - "integrity": "sha512-juWCSrxo/fiMz3RsvDspeSLGmbgC0U9tKqcUPZBCf35s64wlaLXyn2KdHHXVQrb2cqF7I0Hc9siQalainmnXJA==", - "dev": true, - "dependencies": { - "workbox-core": "7.0.0" - } - }, - "apps/tlon-web/node_modules/workbox-range-requests": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-7.0.0.tgz", - "integrity": "sha512-SxAzoVl9j/zRU9OT5+IQs7pbJBOUOlriB8Gn9YMvi38BNZRbM+RvkujHMo8FOe9IWrqqwYgDFBfv6sk76I1yaQ==", - "dev": true, - "dependencies": { - "workbox-core": "7.0.0" - } - }, - "apps/tlon-web/node_modules/workbox-recipes": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-7.0.0.tgz", - "integrity": "sha512-DntcK9wuG3rYQOONWC0PejxYYIDHyWWZB/ueTbOUDQgefaeIj1kJ7pdP3LZV2lfrj8XXXBWt+JDRSw1lLLOnww==", - "dev": true, - "dependencies": { - "workbox-cacheable-response": "7.0.0", - "workbox-core": "7.0.0", - "workbox-expiration": "7.0.0", - "workbox-precaching": "7.0.0", - "workbox-routing": "7.0.0", - "workbox-strategies": "7.0.0" - } - }, - "apps/tlon-web/node_modules/workbox-recipes/node_modules/workbox-precaching": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-7.0.0.tgz", - "integrity": "sha512-EC0vol623LJqTJo1mkhD9DZmMP604vHqni3EohhQVwhJlTgyKyOkMrZNy5/QHfOby+39xqC01gv4LjOm4HSfnA==", - "dev": true, - "dependencies": { - "workbox-core": "7.0.0", - "workbox-routing": "7.0.0", - "workbox-strategies": "7.0.0" - } - }, - "apps/tlon-web/node_modules/workbox-routing": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-7.0.0.tgz", - "integrity": "sha512-8YxLr3xvqidnbVeGyRGkaV4YdlKkn5qZ1LfEePW3dq+ydE73hUUJJuLmGEykW3fMX8x8mNdL0XrWgotcuZjIvA==", - "dev": true, - "dependencies": { - "workbox-core": "7.0.0" - } - }, - "apps/tlon-web/node_modules/workbox-strategies": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-7.0.0.tgz", - "integrity": "sha512-dg3qJU7tR/Gcd/XXOOo7x9QoCI9nk74JopaJaYAQ+ugLi57gPsXycVdBnYbayVj34m6Y8ppPwIuecrzkpBVwbA==", - "dev": true, - "dependencies": { - "workbox-core": "7.0.0" - } - }, - "apps/tlon-web/node_modules/workbox-streams": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-7.0.0.tgz", - "integrity": "sha512-moVsh+5to//l6IERWceYKGiftc+prNnqOp2sgALJJFbnNVpTXzKISlTIsrWY+ogMqt+x1oMazIdHj25kBSq/HQ==", - "dev": true, - "dependencies": { - "workbox-core": "7.0.0", - "workbox-routing": "7.0.0" - } - }, - "apps/tlon-web/node_modules/workbox-sw": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-7.0.0.tgz", - "integrity": "sha512-SWfEouQfjRiZ7GNABzHUKUyj8pCoe+RwjfOIajcx6J5mtgKkN+t8UToHnpaJL5UVVOf5YhJh+OHhbVNIHe+LVA==", - "dev": true - }, "apps/tlon-web/node_modules/yocto-queue": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", @@ -5538,7 +4818,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=12" } @@ -8042,6 +7321,14 @@ "node": ">=8" } }, + "node_modules/@faker-js/faker": { + "version": "6.3.1", + "license": "MIT", + "engines": { + "node": ">=14.0.0", + "npm": ">=6.0.0" + } + }, "node_modules/@floating-ui/core": { "version": "0.7.3", "license": "MIT" @@ -13496,6 +12783,58 @@ "rollup": "^1.20.0||^2.0.0" } }, + "node_modules/@rollup/plugin-replace": { + "version": "5.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "magic-string": "^0.27.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-replace/node_modules/@rollup/pluginutils": { + "version": "5.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-replace/node_modules/magic-string": { + "version": "0.27.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.13" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@rollup/pluginutils": { "version": "3.1.0", "dev": true, @@ -19772,9 +19111,9 @@ "license": "MIT" }, "node_modules/@urbit/http-api": { - "version": "3.1.0-dev-2", - "resolved": "https://registry.npmjs.org/@urbit/http-api/-/http-api-3.1.0-dev-2.tgz", - "integrity": "sha512-9hdleBOvrH3mfgj6GTGowLU91EKOLkLV8VRQAQ3AycPwckWvwgrxn561gn7QSM8YmcdneYnOBH0/zcINUa0A7w==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@urbit/http-api/-/http-api-3.0.0.tgz", + "integrity": "sha512-EmyPbWHWXhfYQ/9wWFcLT53VvCn8ct9ljd6QEe+UBjNPEhUPOFBLpDsDp3iPLQgg8ykSU8JMMHxp95LHCorExA==", "dependencies": { "@babel/runtime": "^7.12.5", "browser-or-node": "^1.3.0", @@ -19791,10 +19130,64 @@ } }, "node_modules/@urbit/vite-plugin-urbit": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@urbit/vite-plugin-urbit/-/vite-plugin-urbit-2.0.1.tgz", - "integrity": "sha512-6v67rT6WbqI9oaC1jn0TJ/Mwu37TxFofh4Ce82W3D9ofKh2fEAeSGAv6i0RcMXG5U2kqiz1r3MJLQzraddCkxA==", - "dev": true + "version": "0.8.0", + "dev": true, + "license": "MIT", + "dependencies": { + "vite-plugin-html-config": "^1.0.6", + "vite-plugin-rewrite-all": "^0.1.2" + } + }, + "node_modules/@urbit/vite-plugin-urbit/node_modules/vite": { + "version": "2.9.16", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.14.27", + "postcss": "^8.4.13", + "resolve": "^1.22.0", + "rollup": ">=2.59.0 <2.78.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": ">=12.2.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "less": "*", + "sass": "*", + "stylus": "*" + }, + "peerDependenciesMeta": { + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + } + } + }, + "node_modules/@urbit/vite-plugin-urbit/node_modules/vite-plugin-rewrite-all": { + "version": "0.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "connect-history-api-fallback": "^1.6.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "vite": "^2.0.2" + } }, "node_modules/@urql/core": { "version": "2.3.6", @@ -19809,9 +19202,9 @@ } }, "node_modules/@vitejs/plugin-basic-ssl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.1.0.tgz", - "integrity": "sha512-wO4Dk/rm8u7RNhOf95ZzcEmC9rYOncYgvq4z3duaJrCgjN8BxAnDVyndanfcJZ0O6XZzHz6Q0hTimxTg8Y9g/A==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.0.2.tgz", + "integrity": "sha512-DKHKVtpI+eA5fvObVgQ3QtTGU70CcCnedalzqmGSR050AzKZMdUzgC8KmlOneHWH8dF2hJ3wkC9+8FDVAaDRCw==", "dev": true, "engines": { "node": ">=14.6.0" @@ -20866,12 +20259,6 @@ "version": "1.0.2", "license": "MIT" }, - "node_modules/base-64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", - "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==", - "peer": true - }, "node_modules/base64-js": { "version": "1.5.1", "funding": [ @@ -21996,6 +21383,14 @@ "node": ">= 0.10.0" } }, + "node_modules/connect-history-api-fallback": { + "version": "1.6.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, "node_modules/connect/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -23275,6 +22670,398 @@ "node": ">=6" } }, + "node_modules/esbuild": { + "version": "0.14.54", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/linux-loong64": "0.14.54", + "esbuild-android-64": "0.14.54", + "esbuild-android-arm64": "0.14.54", + "esbuild-darwin-64": "0.14.54", + "esbuild-darwin-arm64": "0.14.54", + "esbuild-freebsd-64": "0.14.54", + "esbuild-freebsd-arm64": "0.14.54", + "esbuild-linux-32": "0.14.54", + "esbuild-linux-64": "0.14.54", + "esbuild-linux-arm": "0.14.54", + "esbuild-linux-arm64": "0.14.54", + "esbuild-linux-mips64le": "0.14.54", + "esbuild-linux-ppc64le": "0.14.54", + "esbuild-linux-riscv64": "0.14.54", + "esbuild-linux-s390x": "0.14.54", + "esbuild-netbsd-64": "0.14.54", + "esbuild-openbsd-64": "0.14.54", + "esbuild-sunos-64": "0.14.54", + "esbuild-windows-32": "0.14.54", + "esbuild-windows-64": "0.14.54", + "esbuild-windows-arm64": "0.14.54" + } + }, + "node_modules/esbuild-android-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz", + "integrity": "sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-android-arm64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.54.tgz", + "integrity": "sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.54.tgz", + "integrity": "sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-arm64": { + "version": "0.14.54", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.54.tgz", + "integrity": "sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-arm64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.54.tgz", + "integrity": "sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-32": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.54.tgz", + "integrity": "sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz", + "integrity": "sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.54.tgz", + "integrity": "sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.54.tgz", + "integrity": "sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-mips64le": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.54.tgz", + "integrity": "sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-ppc64le": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.54.tgz", + "integrity": "sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-riscv64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.54.tgz", + "integrity": "sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-s390x": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.54.tgz", + "integrity": "sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-netbsd-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.54.tgz", + "integrity": "sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-openbsd-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.54.tgz", + "integrity": "sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-sunos-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz", + "integrity": "sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-32": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.54.tgz", + "integrity": "sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz", + "integrity": "sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-arm64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.54.tgz", + "integrity": "sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-loong64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz", + "integrity": "sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, "node_modules/escalade": { "version": "3.1.1", "license": "MIT", @@ -23901,6 +23688,11 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "2.0.2", + "dev": true, + "license": "MIT" + }, "node_modules/esutils": { "version": "2.0.3", "license": "BSD-2-Clause", @@ -24213,104 +24005,6 @@ "expo": "*" } }, - "node_modules/expo-dev-client": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/expo-dev-client/-/expo-dev-client-3.3.9.tgz", - "integrity": "sha512-qODvuyXe8FgVJhBbwDEk/snZa5wSTNHx+poNXwA/PS4gGvOxCuG+qpeQF6K4Yf6r2+sV0OtigxPJiAyhu9I4ug==", - "dependencies": { - "expo-dev-launcher": "3.6.7", - "expo-dev-menu": "4.5.6", - "expo-dev-menu-interface": "1.7.2", - "expo-manifests": "~0.13.0", - "expo-updates-interface": "~0.15.1" - }, - "peerDependencies": { - "expo": "*" - } - }, - "node_modules/expo-dev-launcher": { - "version": "3.6.7", - "resolved": "https://registry.npmjs.org/expo-dev-launcher/-/expo-dev-launcher-3.6.7.tgz", - "integrity": "sha512-xn0cq2LMXv5t3n4jiAPFd9rwP22GM3zsQqAOJuWXH4b7fRzO8bayxOAt1n4RrDgkVsRzgRnxHm7kkO+Eta3Kzg==", - "dependencies": { - "ajv": "8.11.0", - "expo-dev-menu": "4.5.6", - "expo-manifests": "~0.13.0", - "resolve-from": "^5.0.0", - "semver": "^7.5.3" - }, - "peerDependencies": { - "expo": "*" - } - }, - "node_modules/expo-dev-launcher/node_modules/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/expo-dev-launcher/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "node_modules/expo-dev-launcher/node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/expo-dev-menu": { - "version": "4.5.6", - "resolved": "https://registry.npmjs.org/expo-dev-menu/-/expo-dev-menu-4.5.6.tgz", - "integrity": "sha512-V8gOFrv8JBTy50n9mTWVPKVHMcjvrpI/w5ooZGFzjoerBlPXSauIfRmHsqmgmOr3r5oWptnC2PS3LxuSo4QZ5g==", - "dependencies": { - "expo-dev-menu-interface": "1.7.2", - "semver": "^7.5.3" - }, - "peerDependencies": { - "expo": "*" - } - }, - "node_modules/expo-dev-menu-interface": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/expo-dev-menu-interface/-/expo-dev-menu-interface-1.7.2.tgz", - "integrity": "sha512-V/geSB9rW0IPTR+d7E5CcvkV0uVUCE7SMHZqE/J0/dH06Wo8AahB16fimXeh5/hTL2Qztq8CQ41xpFUBoA9TEw==", - "peerDependencies": { - "expo": "*" - } - }, - "node_modules/expo-dev-menu/node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/expo-device": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/expo-device/-/expo-device-5.9.3.tgz", @@ -24766,11 +24460,6 @@ "node": ">= 12" } }, - "node_modules/fast-base64-decode": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fast-base64-decode/-/fast-base64-decode-1.0.0.tgz", - "integrity": "sha512-qwaScUgUGBYeDNRnbc/KyllVU88Jk1pRHPStuF/lO7B0/RTRLj7U0lkdTAutlBblY08rwZDff6tNU9cjv6j//Q==" - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "license": "MIT" @@ -31013,14 +30702,6 @@ "dev": true, "license": "MIT" }, - "node_modules/p-defer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-3.0.0.tgz", - "integrity": "sha512-ugZxsxmtTln604yeYd29EGrNhazN2lywetzpKhfmQjW/VJmhpDmWbiX+h0zL8V91R0UXkhb3KtPmyq9PZw3aYw==", - "engines": { - "node": ">=8" - } - }, "node_modules/p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", @@ -32851,14 +32532,6 @@ "react-native": "*" } }, - "node_modules/react-native-fetch-api": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/react-native-fetch-api/-/react-native-fetch-api-3.0.0.tgz", - "integrity": "sha512-g2rtqPjdroaboDKTsJCTlcmtw54E25OjyaunUP0anOZn4Fuo2IKs8BVfe02zVggA/UysbmfSnRJIqtNkAgggNA==", - "dependencies": { - "p-defer": "^3.0.0" - } - }, "node_modules/react-native-gesture-handler": { "version": "2.14.1", "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.14.1.tgz", @@ -32875,17 +32548,6 @@ "react-native": "*" } }, - "node_modules/react-native-get-random-values": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/react-native-get-random-values/-/react-native-get-random-values-1.11.0.tgz", - "integrity": "sha512-4BTbDbRmS7iPdhYLRcz3PGFIpFJBwNZg9g42iwa2P6FOv9vZj/xJc678RZXnLNZzd0qd7Q3CCF6Yd+CU2eoXKQ==", - "dependencies": { - "fast-base64-decode": "^1.0.0" - }, - "peerDependencies": { - "react-native": ">=0.56" - } - }, "node_modules/react-native-phone-input": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/react-native-phone-input/-/react-native-phone-input-1.3.7.tgz", @@ -32900,19 +32562,6 @@ "react-native": ">=0.69.9" } }, - "node_modules/react-native-polyfill-globals": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/react-native-polyfill-globals/-/react-native-polyfill-globals-3.1.0.tgz", - "integrity": "sha512-6ACmV1SjXvZP2LN6J2yK58yNACKddcvoiKLrSQdISx32IdYStfdmGXrbAfpd+TANrTlIaZ2SLoFXohNwhnqm/w==", - "peerDependencies": { - "base-64": "*", - "react-native-fetch-api": "*", - "react-native-get-random-values": "*", - "react-native-url-polyfill": "*", - "text-encoding": "*", - "web-streams-polyfill": "*" - } - }, "node_modules/react-native-reanimated": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.6.2.tgz", @@ -32961,11 +32610,6 @@ "react-native": "*" } }, - "node_modules/react-native-sse": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/react-native-sse/-/react-native-sse-1.2.1.tgz", - "integrity": "sha512-zejanlScF+IB9tYnbdry0MT34qjBXbiV/E72qGz33W/tX1bx8MXsbB4lxiuPETc9v/008vYZ60yjIstW22VlVg==" - }, "node_modules/react-native-storage": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/react-native-storage/-/react-native-storage-1.0.1.tgz", @@ -34027,7 +33671,6 @@ "version": "2.77.3", "dev": true, "license": "MIT", - "peer": true, "bin": { "rollup": "dist/bin/rollup" }, @@ -35692,12 +35335,6 @@ "version": "2.20.3", "license": "MIT" }, - "node_modules/text-encoding": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.7.0.tgz", - "integrity": "sha512-oJQ3f1hrOnbRLOcwKz0Liq2IcrvDeZRHXhd9RgLrsT+DjWY/nty1Hi7v3dtkaEYbPYe0mUoOfzRrMwfXXwgPUA==", - "deprecated": "no longer maintained" - }, "node_modules/text-table": { "version": "0.2.0", "license": "MIT" @@ -36365,7 +36002,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", "dev": true, - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -36551,6 +36187,7 @@ }, "node_modules/uri-js": { "version": "4.4.1", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" @@ -36763,7 +36400,6 @@ "version": "4.4.9", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.18.10", "postcss": "^8.4.27", @@ -36991,6 +36627,54 @@ } } }, + "node_modules/vite-plugin-html-config": { + "version": "1.0.9", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "vite": ">=2.0.0" + } + }, + "node_modules/vite-plugin-pwa": { + "version": "0.14.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/plugin-replace": "^5.0.1", + "debug": "^4.3.4", + "fast-glob": "^3.2.12", + "pretty-bytes": "^6.0.0", + "rollup": "^3.7.2", + "workbox-build": "^6.5.4", + "workbox-window": "^6.5.4" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^3.1.0 || ^4.0.0", + "workbox-build": "^6.5.4", + "workbox-window": "^6.5.4" + } + }, + "node_modules/vite-plugin-pwa/node_modules/rollup": { + "version": "3.28.0", + "dev": true, + "license": "MIT", + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/vite/node_modules/@esbuild/android-arm": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", @@ -37003,7 +36687,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=12" } @@ -37020,7 +36703,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=12" } @@ -37037,7 +36719,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=12" } @@ -37054,7 +36735,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=12" } @@ -37071,7 +36751,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=12" } @@ -37088,7 +36767,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=12" } @@ -37105,7 +36783,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=12" } @@ -37122,7 +36799,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=12" } @@ -37139,7 +36815,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=12" } @@ -37156,7 +36831,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=12" } @@ -37173,7 +36847,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=12" } @@ -37190,7 +36863,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=12" } @@ -37207,7 +36879,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=12" } @@ -37224,7 +36895,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=12" } @@ -37241,7 +36911,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=12" } @@ -37258,7 +36927,6 @@ "os": [ "netbsd" ], - "peer": true, "engines": { "node": ">=12" } @@ -37275,7 +36943,6 @@ "os": [ "openbsd" ], - "peer": true, "engines": { "node": ">=12" } @@ -37292,7 +36959,6 @@ "os": [ "sunos" ], - "peer": true, "engines": { "node": ">=12" } @@ -37309,7 +36975,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=12" } @@ -37326,7 +36991,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=12" } @@ -37343,7 +37007,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=12" } @@ -37353,7 +37016,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -37389,7 +37051,6 @@ "version": "3.28.0", "dev": true, "license": "MIT", - "peer": true, "bin": { "rollup": "dist/bin/rollup" }, @@ -37674,14 +37335,6 @@ "@zxing/text-encoding": "0.9.0" } }, - "node_modules/web-streams-polyfill": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", - "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", - "engines": { - "node": ">= 8" - } - }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -37874,53 +37527,292 @@ "resolved": "https://registry.npmjs.org/wonka/-/wonka-4.0.15.tgz", "integrity": "sha512-U0IUQHKXXn6PFo9nqsHphVCE5m3IntqZNB9Jjn7EB1lrR7YTDY3YWgFvEvwniTzXSvOH/XMzAZaIfJF/LvHYXg==" }, + "node_modules/workbox-background-sync": { + "version": "6.5.4", + "dev": true, + "license": "MIT", + "dependencies": { + "idb": "^7.0.1", + "workbox-core": "6.5.4" + } + }, + "node_modules/workbox-broadcast-update": { + "version": "6.5.4", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "6.5.4" + } + }, + "node_modules/workbox-build": { + "version": "6.5.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@apideck/better-ajv-errors": "^0.3.1", + "@babel/core": "^7.11.1", + "@babel/preset-env": "^7.11.0", + "@babel/runtime": "^7.11.2", + "@rollup/plugin-babel": "^5.2.0", + "@rollup/plugin-node-resolve": "^11.2.1", + "@rollup/plugin-replace": "^2.4.1", + "@surma/rollup-plugin-off-main-thread": "^2.2.3", + "ajv": "^8.6.0", + "common-tags": "^1.8.0", + "fast-json-stable-stringify": "^2.1.0", + "fs-extra": "^9.0.1", + "glob": "^7.1.6", + "lodash": "^4.17.20", + "pretty-bytes": "^5.3.0", + "rollup": "^2.43.1", + "rollup-plugin-terser": "^7.0.0", + "source-map": "^0.8.0-beta.0", + "stringify-object": "^3.3.0", + "strip-comments": "^2.0.1", + "tempy": "^0.6.0", + "upath": "^1.2.0", + "workbox-background-sync": "6.5.4", + "workbox-broadcast-update": "6.5.4", + "workbox-cacheable-response": "6.5.4", + "workbox-core": "6.5.4", + "workbox-expiration": "6.5.4", + "workbox-google-analytics": "6.5.4", + "workbox-navigation-preload": "6.5.4", + "workbox-precaching": "6.5.4", + "workbox-range-requests": "6.5.4", + "workbox-recipes": "6.5.4", + "workbox-routing": "6.5.4", + "workbox-strategies": "6.5.4", + "workbox-streams": "6.5.4", + "workbox-sw": "6.5.4", + "workbox-window": "6.5.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/workbox-build/node_modules/@apideck/better-ajv-errors": { + "version": "0.3.6", + "dev": true, + "license": "MIT", + "dependencies": { + "json-schema": "^0.4.0", + "jsonpointer": "^5.0.0", + "leven": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "ajv": ">=8" + } + }, + "node_modules/workbox-build/node_modules/@rollup/plugin-replace": { + "version": "2.4.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^3.1.0", + "magic-string": "^0.25.7" + }, + "peerDependencies": { + "rollup": "^1.20.0 || ^2.0.0" + } + }, + "node_modules/workbox-build/node_modules/ajv": { + "version": "8.12.0", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/workbox-build/node_modules/fs-extra": { + "version": "9.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/workbox-build/node_modules/json-schema-traverse": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/workbox-build/node_modules/magic-string": { + "version": "0.25.9", + "dev": true, + "license": "MIT", + "dependencies": { + "sourcemap-codec": "^1.4.8" + } + }, + "node_modules/workbox-build/node_modules/pretty-bytes": { + "version": "5.6.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/workbox-build/node_modules/source-map": { + "version": "0.8.0-beta.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "whatwg-url": "^7.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/workbox-build/node_modules/tr46": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/workbox-build/node_modules/webidl-conversions": { + "version": "4.0.2", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/workbox-build/node_modules/whatwg-url": { + "version": "7.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "node_modules/workbox-cacheable-response": { + "version": "6.5.4", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "6.5.4" + } + }, "node_modules/workbox-core": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-6.6.0.tgz", - "integrity": "sha512-GDtFRF7Yg3DD859PMbPAYPeJyg5gJYXuBQAC+wyrWuuXgpfoOrIQIvFRZnQ7+czTIQjIr1DhLEGFzZanAT/3bQ==" + "version": "6.5.4", + "license": "MIT" + }, + "node_modules/workbox-expiration": { + "version": "6.5.4", + "dev": true, + "license": "MIT", + "dependencies": { + "idb": "^7.0.1", + "workbox-core": "6.5.4" + } + }, + "node_modules/workbox-google-analytics": { + "version": "6.5.4", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-background-sync": "6.5.4", + "workbox-core": "6.5.4", + "workbox-routing": "6.5.4", + "workbox-strategies": "6.5.4" + } + }, + "node_modules/workbox-navigation-preload": { + "version": "6.5.4", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "6.5.4" + } }, "node_modules/workbox-precaching": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-6.6.0.tgz", - "integrity": "sha512-eYu/7MqtRZN1IDttl/UQcSZFkHP7dnvr/X3Vn6Iw6OsPMruQHiVjjomDFCNtd8k2RdjLs0xiz9nq+t3YVBcWPw==", + "version": "6.5.4", + "license": "MIT", "dependencies": { - "workbox-core": "6.6.0", - "workbox-routing": "6.6.0", - "workbox-strategies": "6.6.0" + "workbox-core": "6.5.4", + "workbox-routing": "6.5.4", + "workbox-strategies": "6.5.4" + } + }, + "node_modules/workbox-range-requests": { + "version": "6.5.4", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "6.5.4" + } + }, + "node_modules/workbox-recipes": { + "version": "6.5.4", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-cacheable-response": "6.5.4", + "workbox-core": "6.5.4", + "workbox-expiration": "6.5.4", + "workbox-precaching": "6.5.4", + "workbox-routing": "6.5.4", + "workbox-strategies": "6.5.4" } }, "node_modules/workbox-routing": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-6.6.0.tgz", - "integrity": "sha512-x8gdN7VDBiLC03izAZRfU+WKUXJnbqt6PG9Uh0XuPRzJPpZGLKce/FkOX95dWHRpOHWLEq8RXzjW0O+POSkKvw==", + "version": "6.5.4", + "license": "MIT", "dependencies": { - "workbox-core": "6.6.0" + "workbox-core": "6.5.4" } }, "node_modules/workbox-strategies": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-6.6.0.tgz", - "integrity": "sha512-eC07XGuINAKUWDnZeIPdRdVja4JQtTuc35TZ8SwMb1ztjp7Ddq2CJ4yqLvWzFWGlYI7CG/YGqaETntTxBGdKgQ==", + "version": "6.5.4", + "license": "MIT", "dependencies": { - "workbox-core": "6.6.0" + "workbox-core": "6.5.4" } }, + "node_modules/workbox-streams": { + "version": "6.5.4", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "6.5.4", + "workbox-routing": "6.5.4" + } + }, + "node_modules/workbox-sw": { + "version": "6.5.4", + "dev": true, + "license": "MIT" + }, "node_modules/workbox-window": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-7.0.0.tgz", - "integrity": "sha512-j7P/bsAWE/a7sxqTzXo3P2ALb1reTfZdvVp6OJ/uLr/C2kZAMvjeWGm8V4htQhor7DOvYg0sSbFN2+flT5U0qA==", + "version": "6.5.4", "dev": true, + "license": "MIT", "dependencies": { "@types/trusted-types": "^2.0.2", - "workbox-core": "7.0.0" + "workbox-core": "6.5.4" } }, - "node_modules/workbox-window/node_modules/workbox-core": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-7.0.0.tgz", - "integrity": "sha512-81JkAAZtfVP8darBpfRTovHg8DGAVrKFgHpOArZbdFd78VqHr5Iw65f2guwjE2NlCFbPFDoez3D3/6ZvhI/rwQ==", - "dev": true - }, "node_modules/wrap-ansi": { "version": "7.0.0", "license": "MIT", @@ -38204,7 +38096,6 @@ "packages/shared": { "name": "@tloncorp/shared", "version": "1.0.0", - "hasInstallScript": true, "dependencies": { "big-integer": "^1.6.52", "sorted-btree": "^1.8.1" @@ -39154,6 +39045,206 @@ "engines": { "node": ">= 14" } + }, + "ui": { + "name": "landscape-apps", + "version": "5.5.0", + "extraneous": true, + "dependencies": { + "@aws-sdk/client-s3": "^3.190.0", + "@aws-sdk/s3-request-presigner": "^3.190.0", + "@emoji-mart/data": "^1.0.6", + "@emoji-mart/react": "^1.0.1", + "@faker-js/faker": "^6.3.1", + "@radix-ui/react-accordion": "^1.1.2", + "@radix-ui/react-collapsible": "^1.0.3", + "@radix-ui/react-dialog": "^1.0.4", + "@radix-ui/react-dropdown-menu": "^2.0.5", + "@radix-ui/react-popover": "^1.0.2", + "@radix-ui/react-radio-group": "^1.1.3", + "@radix-ui/react-tabs": "^1.0.4", + "@radix-ui/react-toast": "^1.0.0", + "@radix-ui/react-toggle": "^1.0.2", + "@radix-ui/react-toggle-group": "^1.0.4", + "@radix-ui/react-tooltip": "^1.0.0", + "@tanstack/react-query": "^4.28.0", + "@tanstack/react-query-devtools": "^4.28.0", + "@tanstack/react-query-persist-client": "^4.28.0", + "@tanstack/react-virtual": "^3.0.0-beta.60", + "@tiptap/core": "^2.0.3", + "@tiptap/extension-blockquote": "^2.0.3", + "@tiptap/extension-bold": "^2.0.3", + "@tiptap/extension-bullet-list": "^2.0.3", + "@tiptap/extension-code": "^2.0.3", + "@tiptap/extension-code-block": "^2.0.3", + "@tiptap/extension-document": "^2.0.3", + "@tiptap/extension-floating-menu": "^2.0.3", + "@tiptap/extension-hard-break": "^2.0.3", + "@tiptap/extension-heading": "^2.0.3", + "@tiptap/extension-history": "^2.0.3", + "@tiptap/extension-horizontal-rule": "^2.0.3", + "@tiptap/extension-italic": "^2.0.3", + "@tiptap/extension-link": "^2.0.3", + "@tiptap/extension-list-item": "^2.0.3", + "@tiptap/extension-mention": "^2.0.3", + "@tiptap/extension-ordered-list": "^2.0.3", + "@tiptap/extension-paragraph": "^2.0.3", + "@tiptap/extension-placeholder": "^2.0.3", + "@tiptap/extension-strike": "^2.0.3", + "@tiptap/extension-task-item": "^2.1.7", + "@tiptap/extension-task-list": "^2.1.7", + "@tiptap/extension-text": "^2.0.3", + "@tiptap/pm": "^2.0.3", + "@tiptap/react": "^2.0.3", + "@tiptap/suggestion": "^2.0.3", + "@tloncorp/mock-http-api": "^1.2.0", + "@tloncorp/shared": "*", + "@types/marked": "^4.3.0", + "@urbit/api": "^2.2.0", + "@urbit/aura": "^1.0.0", + "@urbit/http-api": "^3.0.0", + "@urbit/sigil-js": "^2.1.0", + "any-ascii": "^0.3.1", + "big-integer": "^1.6.51", + "browser-cookies": "^1.2.0", + "browser-image-compression": "^2.0.2", + "classnames": "^2.3.1", + "clipboard-copy": "^4.0.1", + "color2k": "^2.0.0", + "cross-fetch": "^3.1.5", + "date-fns": "^2.28.0", + "dompurify": "^3.0.6", + "emoji-mart": "^5.2.2", + "emoji-regex": "^10.2.1", + "fast-average-color": "^9.1.1", + "framer-motion": "^6.5.1", + "fuzzy": "^0.1.3", + "get-youtube-id": "^1.0.1", + "hast-to-hyperscript": "^10.0.1", + "history": "^5.3.0", + "idb-keyval": "^6.2.0", + "immer": "^9.0.12", + "lodash": "^4.17.21", + "marked": "^4.3.0", + "masonic": "^3.6.5", + "moment": "^2.29.4", + "posthog-js": "^1.68.2", + "prismjs": "^1.29.0", + "prosemirror-commands": "~1.2.2", + "prosemirror-history": "~1.2.0", + "prosemirror-keymap": "~1.1.5", + "prosemirror-markdown": "^1.11.1", + "prosemirror-model": "~1.16.1", + "prosemirror-schema-list": "~1.1.6", + "prosemirror-state": "~1.3.4", + "prosemirror-transform": "~1.4.2", + "prosemirror-view": "~1.23.13", + "react": "^18.2.0", + "react-beautiful-dnd": "^13.1.1", + "react-colorful": "^5.5.1", + "react-dnd": "^15.1.1", + "react-dnd-html5-backend": "^15.1.2", + "react-dnd-touch-backend": "^15.1.1", + "react-dom": "^18.2.0", + "react-error-boundary": "^3.1.4", + "react-helmet": "^6.1.0", + "react-hook-form": "^7.30.0", + "react-image-size": "^2.0.0", + "react-intersection-observer": "^9.4.0", + "react-oembed-container": "github:stefkampen/react-oembed-container", + "react-qr-code": "^2.0.12", + "react-router": "^6.22.1", + "react-router-dom": "^6.22.1", + "react-select": "^5.3.2", + "react-tweet": "^3.0.4", + "react-virtuoso": "^4.5.1", + "refractor": "^4.8.0", + "slugify": "^1.6.6", + "sorted-btree": "^1.8.1", + "tailwindcss-opentype": "^1.1.0", + "tailwindcss-scoped-groups": "^2.0.0", + "tippy.js": "^6.3.7", + "urbit-ob": "^5.0.1", + "use-pwa-install": "^1.0.1", + "usehooks-ts": "^2.6.0", + "uuid": "^9.0.0", + "validator": "^13.7.0", + "vaul": "github:latter-bolden/vaul", + "video-react": "^0.16.0", + "workbox-precaching": "^6.5.4", + "zustand": "^3.7.2" + }, + "devDependencies": { + "@playwright/test": "^1.33.0", + "@tailwindcss/aspect-ratio": "^0.4.0", + "@tailwindcss/container-queries": "^0.1.0", + "@tailwindcss/typography": "^0.5.7", + "@testing-library/jest-dom": "^5.16.4", + "@testing-library/react": "^14.0.0", + "@testing-library/user-event": "^14.2.0", + "@tloncorp/eslint-config": "^0.0.6", + "@types/dompurify": "^3.0.5", + "@types/fs-extra": "^11.0.1", + "@types/lodash": "4.14.183", + "@types/node": "^20.10.8", + "@types/node-fetch": "^2.6.4", + "@types/portscanner": "^2.1.1", + "@types/prismjs": "^1.26.0", + "@types/qrcode": "^1.5.2", + "@types/react": "^18.2.46", + "@types/react-beautiful-dnd": "^13.1.2", + "@types/react-dom": "^18.2.7", + "@types/react-helmet": "^6.1.5", + "@types/react-test-renderer": "^18.0.0", + "@types/tar-fs": "^2.0.1", + "@types/uuid": "^9.0.2", + "@types/validator": "^13.7.2", + "@types/video-react": "^0.15.1", + "@types/ws": "^8.5.3", + "@typescript-eslint/eslint-plugin": "^5.19.0", + "@typescript-eslint/parser": "^5.19.0", + "@urbit/eslint-config": "^1.0.3", + "@urbit/vite-plugin-urbit": "^0.8.0", + "@vitejs/plugin-basic-ssl": "^1.0.1", + "@vitejs/plugin-react": "^4.0.3", + "@welldone-software/why-did-you-render": "^7.0.1", + "autoprefixer": "^10.4.4", + "concurrently": "^8.0.1", + "cross-env": "^7.0.3", + "eslint": "^8.13.0", + "eslint-config-airbnb": "^19.0.4", + "eslint-config-prettier": "^9.1.0", + "eslint-import-resolver-alias": "^1.1.2", + "eslint-import-resolver-typescript": "^3.6.1", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-prettier": "^5.1.3", + "eslint-plugin-react": "^7.29.4", + "eslint-plugin-react-hooks": "^4.4.0", + "eslint-plugin-tailwindcss": "^3.5.0", + "fs-extra": "^11.1.1", + "husky": "^8.0.3", + "jsdom": "^23.2.0", + "lint-staged": "^15.0.0", + "msw": "^0.44.2", + "node-fetch": "^2.6.12", + "postcss": "^8.4.12", + "postcss-import": "^14.1.0", + "prettier": "^3.1.1", + "react-cosmos": "6.0.0-beta.6", + "react-cosmos-plugin-vite": "6.0.0-beta.6", + "react-test-renderer": "^18.2.0", + "rollup-plugin-analyzer": "^4.0.0", + "rollup-plugin-visualizer": "^5.6.0", + "tailwindcss": "^3.2.7", + "tailwindcss-theme-swapper": "^0.7.3", + "tar-fs": "^3.0.4", + "tsc-files": "^1.1.3", + "typescript": "^4.6.3", + "vite": "^4.4.3", + "vite-plugin-pwa": "^0.14.4", + "vitest": "^0.34.1" + } } } } diff --git a/package.json b/package.json index 4bd521dc6f..4eaaf84939 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "dev:web": "concurrently \"npm run dev:shared\" \"npm run dev-no-ssl --prefix ./apps/tlon-web\"", "test": "npm run test --workspaces --if-present -- run -u", "prepare": "husky", - "postinstall": "npx patch-package" + "postinstall": "npx patch-package && npm run build:shared" }, "devDependencies": { "concurrently": "^8.2.2", diff --git a/packages/shared/package.json b/packages/shared/package.json index a487561770..a34a32b060 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -6,8 +6,7 @@ "types": "./dist/index.d.ts", "scripts": { "build": "tsup src/index.ts src/urbit/* --format esm --minify --dts --out-dir dist", - "dev": "npm run build -- --watch", - "postinstall": "npm run build" + "dev": "npm run build -- --watch" }, "dependencies": { "big-integer": "^1.6.52", diff --git a/packages/shared/src/client/index.ts b/packages/shared/src/client/index.ts deleted file mode 100644 index cd9a695cae..0000000000 --- a/packages/shared/src/client/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -export namespace ClientTypes { - export type Contact = { - id: string; - nickname: string | null; - bio: string | null; - status: string | null; - color: string | null; - avatarImage: string | null; - coverImage: string | null; - pinnedGroupIds: string[]; - }; - - export type UnreadType = "channel" | "dm"; - export type Unread = { - channelId: string; - type: UnreadType; - totalCount: number; - }; -} diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 702187c6ef..6dcb9f2cf4 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -9,4 +9,3 @@ export type { WebAppCommand, } from "./types/native"; export { parseActiveTab, trimFullPath } from "./logic/navigation"; -export type { ClientTypes } from "./client"; diff --git a/packages/shared/src/types/native.ts b/packages/shared/src/types/native.ts index c832c9f2c2..1950423541 100644 --- a/packages/shared/src/types/native.ts +++ b/packages/shared/src/types/native.ts @@ -1,7 +1,6 @@ export interface NativeWebViewOptions { colorScheme?: "light" | "dark" | null; hideTabBar?: boolean; - isUsingTlonAuth?: boolean; safeAreaInsets?: { top: number; bottom: number; diff --git a/packages/shared/src/urbit/channel.ts b/packages/shared/src/urbit/channel.ts index 5824cba539..fdd639326a 100644 --- a/packages/shared/src/urbit/channel.ts +++ b/packages/shared/src/urbit/channel.ts @@ -1,5 +1,4 @@ -import { udToDec } from '@urbit/api'; -import bigInt, { BigInteger } from 'big-integer'; +import { BigInteger } from 'big-integer'; import _ from 'lodash'; import BTree from 'sorted-btree'; @@ -179,7 +178,6 @@ export interface PostEssay { export type Post = { seal: PostSeal; essay: PostEssay; - revision?: string; }; export interface PagedPosts { @@ -197,7 +195,7 @@ export interface Posts { [time: string]: Post | null; } -export type PostTuple = [BigInteger, Post | null]; +export type PageTuple = [BigInteger, Post | null]; export type ReplyTuple = [BigInteger, Reply | null]; @@ -206,7 +204,6 @@ export type PageMap = BTree; export interface Reply { seal: ReplySeal; memo: Memo; - revision?: string; } export interface Memo { @@ -297,13 +294,6 @@ export interface ReplyActionAdd { add: Memo; } -export interface ReplyActionEdit { - edit: { - id: string; - memo: Memo; - }; -} - export interface ReplyActionDel { del: string; } @@ -311,7 +301,6 @@ export interface ReplyActionDel { export type ReplyAction = | ReplyActionAdd | ReplyActionDel - | ReplyActionEdit | PostActionAddReact | PostActionDelReact; @@ -462,15 +451,16 @@ export function isCite(s: Block): boolean { export function blockContentIsImage(content: Story) { return ( content.length > 0 && - content.filter(c => 'block' in c).length > 0 && - isImage((content.filter(c => 'block' in c)[0] as VerseBlock).block) + content.filter((c) => 'block' in c).length > 0 && + isImage((content.filter((c) => 'block' in c)[0] as VerseBlock).block) ); } export function imageUrlFromContent(content: Story) { if (blockContentIsImage(content)) { - return ((content.filter(c => 'block' in c)[0] as VerseBlock).block as Image) - .image.src; + return ( + (content.filter((c) => 'block' in c)[0] as VerseBlock).block as Image + ).image.src; } return undefined; } @@ -478,16 +468,16 @@ export function imageUrlFromContent(content: Story) { export function chatStoryFromStory(story: Story): ChatStory { const newCon: ChatStory = { inline: [], - block: [] + block: [], }; const inlines: Inline[] = story - .filter(s => 'inline' in s) - .map(s => (s as VerseInline).inline) + .filter((s) => 'inline' in s) + .map((s) => (s as VerseInline).inline) .flat(); const blocks: ChatBlock[] = story - .filter(s => 'block' in s) - .map(s => (s as VerseBlock).block as ChatBlock) + .filter((s) => 'block' in s) + .map((s) => (s as VerseBlock).block as ChatBlock) .flat(); newCon.inline = inlines; @@ -504,7 +494,7 @@ export function storyFromChatStory(chatStory: ChatStory): Story { newStory.push({ inline: inlines }); - blocks.forEach(b => { + blocks.forEach((b) => { newStory.push({ block: b }); }); @@ -541,30 +531,28 @@ export const emptyPost: Post = { meta: { replyCount: 0, lastRepliers: [], - lastReply: null - } + lastReply: null, + }, }, - revision: '0', essay: { author: '', content: [], sent: 0, - 'kind-data': { chat: null } - } + 'kind-data': { chat: null }, + }, }; export const emptyReply: Reply = { seal: { id: '', 'parent-id': '', - reacts: {} + reacts: {}, }, - revision: '0', memo: { author: '', content: [], - sent: 0 - } + sent: 0, + }, }; export function constructStory( @@ -582,8 +570,8 @@ export function constructStory( 'header', 'rule', 'cite', - codeAsBlock ? 'code' : '' - ].some(k => typeof c !== 'string' && k in c); + codeAsBlock ? 'code' : '', + ].some((k) => typeof c !== 'string' && k in c); const postContent: Story = []; let index = 0; data.forEach((c, i) => { @@ -597,7 +585,7 @@ export function constructStory( } else { const inline = _.takeWhile( _.drop(data, index), - d => !isBlock(d) + (d) => !isBlock(d) ) as Inline[]; postContent.push({ inline }); index += inline.length; @@ -616,32 +604,7 @@ export function newReplyMap( ); } -export function newPostTupleArray( - data: - | { - pages: PagedPosts[]; - } - | undefined -): PostTuple[] { - if (data === undefined || data.pages.length === 0) { - return []; - } - - return _.uniqBy( - data.pages - .map(page => { - const pagePosts = Object.entries(page.posts).map( - ([k, v]) => [bigInt(udToDec(k)), v] as PostTuple - ); - - return pagePosts; - }) - .flat(), - ([k]) => k.toString() - ).sort(([a], [b]) => a.compare(b)); -} - -export function newPostMap(entries?: PostTuple[], reverse = false): PageMap { +export function newPostMap(entries?: PageTuple[], reverse = false): PageMap { return new BTree(entries, (a, b) => reverse ? b.compare(a) : a.compare(b) ); @@ -673,7 +636,6 @@ export interface PostSealDataResponse { export interface PostDataResponse { seal: PostSealDataResponse; - revision?: string; essay: PostEssay; } diff --git a/packages/shared/src/urbit/dms.ts b/packages/shared/src/urbit/dms.ts index b4e7c71d06..4bc455b881 100644 --- a/packages/shared/src/urbit/dms.ts +++ b/packages/shared/src/urbit/dms.ts @@ -1,6 +1,4 @@ -import { udToDec } from '@urbit/api'; -import bigInt, { BigInteger } from 'big-integer'; -import _ from 'lodash'; +import { BigInteger } from 'big-integer'; import BTree from 'sorted-btree'; import { @@ -180,30 +178,6 @@ export function newWritMap(entries?: WritTuple[], reverse = false): WritMap { ); } -export function newWritTupleArray( - data: - | { - pages: PagedWrits[]; - } - | undefined -): WritTuple[] { - if (data === undefined || data.pages.length === 0) { - return []; - } - - return _.uniqBy( - data?.pages - ?.map((page) => { - const writPages = Object.entries(page.writs).map( - ([k, v]) => [bigInt(udToDec(k)), v] as WritTuple - ); - return writPages; - }) - .flat() || [], - ([k]) => k.toString() - ).sort(([a], [b]) => a.compare(b)); -} - export interface Pact { writs: WritMap; index: { diff --git a/packages/ui/src/components/Avatar.tsx b/packages/ui/src/components/Avatar.tsx deleted file mode 100644 index 91d5bae56c..0000000000 --- a/packages/ui/src/components/Avatar.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { Image, View, ViewProps } from "tamagui"; -import { UrbitSigil } from "./UrbitSigil"; -import type { ClientTypes as Client } from "../../../shared"; - -export function Avatar({ - contact, - ...props -}: { - contact: Client.Contact; -} & ViewProps) { - // Note, the web Avatar component additionally checks calm settings and confirms the link is valid. - if (contact?.avatarImage) { - return ( - - - - ); - } - - return ; -} diff --git a/packages/ui/src/components/UrbitSigil.tsx b/packages/ui/src/components/UrbitSigil.tsx index f9fb024a21..5bd0766093 100644 --- a/packages/ui/src/components/UrbitSigil.tsx +++ b/packages/ui/src/components/UrbitSigil.tsx @@ -6,7 +6,6 @@ import { useTheme, View } from "tamagui"; export const UrbitSigil = View.styleable<{ ship: string; }>(({ ship, ...props }, ref) => { - const validShip = ship.length <= 14; // planet or larger const theme = useTheme(); const sigilXml = useMemo( () => @@ -31,7 +30,7 @@ export const UrbitSigil = View.styleable<{ borderRadius="$2xs" {...props} > - {validShip && } +
); }); diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 748b797d53..335294655d 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -3,5 +3,4 @@ export * from "./components/TamaguiProvider"; export * from "./tamagui.config"; export * from "./components/Icon"; export * from "./components/UrbitSigil"; -export * from "./components/Avatar"; -export { ZStack, View, Circle } from "tamagui"; +export { ZStack, View } from "tamagui"; diff --git a/patches/@tiptap+react+2.0.3.patch b/patches/@tiptap+react+2.0.3.patch deleted file mode 100644 index 3ceec1a19a..0000000000 --- a/patches/@tiptap+react+2.0.3.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/node_modules/@tiptap/react/dist/index.js b/node_modules/@tiptap/react/dist/index.js -index ca146b7..0b3b451 100644 ---- a/node_modules/@tiptap/react/dist/index.js -+++ b/node_modules/@tiptap/react/dist/index.js -@@ -58,7 +58,7 @@ class PureEditorContent extends React.Component { - } - init() { - const { editor } = this.props; -- if (editor && editor.options.element) { -+ if (editor && !editor.isDestroyed && editor.options.element) { - if (editor.contentComponent) { - return; - } diff --git a/patches/@urbit+http-api+3.1.0-dev-2.patch b/patches/@urbit+http-api+3.0.0.patch similarity index 65% rename from patches/@urbit+http-api+3.1.0-dev-2.patch rename to patches/@urbit+http-api+3.0.0.patch index 491c38be5f..93f297e39a 100644 --- a/patches/@urbit+http-api+3.1.0-dev-2.patch +++ b/patches/@urbit+http-api+3.0.0.patch @@ -1,13 +1,13 @@ diff --git a/node_modules/@urbit/http-api/dist/esm/index.js b/node_modules/@urbit/http-api/dist/esm/index.js -index 5575d46..9ec251e 100644 +index 2e6756f..32ca00a 100644 --- a/node_modules/@urbit/http-api/dist/esm/index.js +++ b/node_modules/@urbit/http-api/dist/esm/index.js -@@ -446,10 +446,11 @@ class Urbit { +@@ -442,10 +442,11 @@ class Urbit { * be the empty string. * @param code The access code for the ship at that address */ -- constructor(url, code, desk, fetchFn) { -+ constructor(url, code, desk, fetchFn, urlTransformer = (url) => url) { +- constructor(url, code, desk) { ++ constructor(url, code, desk, urlTransformer = (url) => url) { this.url = url; this.code = code; this.desk = desk; @@ -15,12 +15,12 @@ index 5575d46..9ec251e 100644 if (isBrowser_1) { window.addEventListener('beforeunload', this.delete); } -@@ -784,7 +785,7 @@ class Urbit { +@@ -776,7 +777,7 @@ class Urbit { return eventId; } async sendJSONtoChannel(...json) { -- const response = await this.fetchFn(this.channelUrl, { -+ const response = await this.fetchFn(this.urlTransformer(this.channelUrl, json), { +- const response = await fetch(this.channelUrl, { ++ const response = await fetch(this.urlTransformer(this.channelUrl, json), { ...this.fetchOptions, method: 'PUT', body: JSON.stringify(json), diff --git a/patches/vite-plugin-pwa+0.14.4.patch b/patches/vite-plugin-pwa+0.14.4.patch new file mode 100644 index 0000000000..67b9a638f1 --- /dev/null +++ b/patches/vite-plugin-pwa+0.14.4.patch @@ -0,0 +1,34 @@ +diff --git a/node_modules/vite-plugin-pwa/dist/index.js b/node_modules/vite-plugin-pwa/dist/index.js +index 6450db5..bc0ab69 100644 +--- a/node_modules/vite-plugin-pwa/dist/index.js ++++ b/node_modules/vite-plugin-pwa/dist/index.js +@@ -121,9 +121,9 @@ function generateRegisterSW(options2, dev) { + return `