diff --git a/frontend/package.json b/frontend/package.json index f0ece8de0f..f63c8f58ad 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -78,6 +78,7 @@ "@tailwindcss/typography": "^0.5.16", "@tanstack/history": "^1.133.28", "@tanstack/query-core": "^5.87.1", + "@tanstack/react-hotkeys": "^0.3.0", "@tanstack/react-query": "^5.87.1", "@tanstack/react-query-devtools": "^5.87.3", "@tanstack/react-router": "^1.131.36", diff --git a/frontend/src/app/dev-toolbar.tsx b/frontend/src/app/dev-toolbar.tsx new file mode 100644 index 0000000000..4934fdaee2 --- /dev/null +++ b/frontend/src/app/dev-toolbar.tsx @@ -0,0 +1,166 @@ +import { useAuth, useOrganization, useUser } from "@clerk/clerk-react"; +import * as Sentry from "@sentry/react"; +import { + formatForDisplay, + type Hotkey, + useHotkeySequence, + useKeyHold, +} from "@tanstack/react-hotkeys"; +import { useQueryClient } from "@tanstack/react-query"; +import { usePostHog } from "posthog-js/react"; +import { useEffect, useState } from "react"; +import { cn, Kbd, ls } from "@/components"; + +export const DevToolbar = () => { + if (__APP_TYPE__ !== "cloud") return null; + if ( + ls.get( + "__I_SOLELY_SWORE_TO_ONLY_ENABLE_DEV_TOOLBAR_FOR_DEBUGGING_PURPOSES_AND_WILL_NOT_USE_IT_FOR_ANY_MALICIOUS_ACTIVITIES__", + ) !== true + ) { + return null; + } + + return ; +}; + +const Content = () => { + const { userId, actor } = useAuth(); + const { user } = useUser(); + const { organization: org } = useOrganization(); + + const [, setState] = useState({}); // used just to trigger re-render every second + + useEffect(() => { + const interval = setInterval(() => { + setState({}); + }, 1000); + return () => clearInterval(interval); + }, []); + + const posthog = usePostHog(); + const queryClient = useQueryClient(); + + const debugSequence: Hotkey = "Mod+Shift+G"; + + // recording start + useHotkeySequence([debugSequence, "R", "S"], () => { + posthog.startSessionRecording(); + }); + // recording stop + useHotkeySequence([debugSequence, "R", "T"], () => { + posthog.stopSessionRecording(); + }); + // load toolbar + useHotkeySequence([debugSequence, "R", "L"], () => { + posthog.toolbar.loadToolbar(); + }); + // clear cache + useHotkeySequence([debugSequence, "C"], () => { + queryClient.clear(); + }); + // report issue + useHotkeySequence([debugSequence, "I"], () => { + Sentry.showReportDialog(); + }); + + return ( +
+
+
+ {__APP_TYPE__}{" "} + {__APP_BUILD_ID__} +
+ +
+ {actor ? ( + + {actor?.sub || "Unknown"} is signed in as{" "} + {userId || "Unknown"} + + ) : null} + + {user?.primaryEmailAddress?.emailAddress || "Unknown"} + + + {user?.id || "Unknown"} {org?.id || "Unknown"} + +
+ + +
+ rec {posthog.sessionRecording?.status} +
+ {" "} + +
{" "} + {" "} + ⋅{" "} + {" "} + +
+ + +
+ {" "} + +
+ +
+ + +
+
+
+ ); +}; + +function ShortcutBadge({ hotkey }: { hotkey: Hotkey | (string & {}) }) { + const isShiftHeld = useKeyHold("Shift"); + if (isShiftHeld) { + return {formatForDisplay(hotkey)}; + } + return null; +} + +const Sep = () => ; diff --git a/frontend/src/routes/__root.tsx b/frontend/src/routes/__root.tsx index bea7d11387..38233efd0f 100644 --- a/frontend/src/routes/__root.tsx +++ b/frontend/src/routes/__root.tsx @@ -17,6 +17,7 @@ import type { OrganizationContext, ProjectContext, } from "@/app/data-providers/cache"; +import { DevToolbar } from "@/app/dev-toolbar"; import { FullscreenLoading } from "@/components"; import { clerk } from "@/lib/auth"; import { cloudEnv } from "@/lib/env"; @@ -64,6 +65,8 @@ function CloudRoute() { }} > + + {import.meta.env.DEV ? ( ) : null} diff --git a/package.json b/package.json index fc6f073f52..1138fc4967 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "@rivetkit/engine-api-full": "workspace:*", "@types/react": "^19", "@types/react-dom": "^19", - "@clerk/shared": "3.27.1" + "@clerk/shared": "^3.27.1" }, "pnpm": { "overrides": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c83b42cac5..9d86ac7dad 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,7 +14,7 @@ overrides: '@rivetkit/engine-api-full': workspace:* '@types/react': ^19 '@types/react-dom': ^19 - '@clerk/shared': 3.27.1 + '@clerk/shared': ^3.27.1 react: 19.1.0 react-dom: 19.1.0 '@rivet-gg/cloud': https://pkg.pr.new/rivet-dev/cloud/@rivet-gg/cloud@edc6ad0 @@ -3376,7 +3376,7 @@ importers: specifier: ^0.23.63 version: 0.23.63(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(next@15.5.9(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.93.2))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@clerk/shared': - specifier: 3.27.1 + specifier: ^3.27.1 version: 3.27.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@clerk/themes': specifier: ^2.4.18 @@ -3534,6 +3534,9 @@ importers: '@tanstack/query-core': specifier: ^5.87.1 version: 5.87.1 + '@tanstack/react-hotkeys': + specifier: ^0.3.0 + version: 0.3.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@tanstack/react-query': specifier: ^5.87.1 version: 5.87.1(react@19.1.0) @@ -9182,12 +9185,23 @@ packages: resolution: {integrity: sha512-B7+x7eP2FFvi3fgd3rNH9o/Eixt+pp0zCIdGhnQbAJjFrlwIKGjGnwyJjhWJ5fMQlGks/E2LdDTqEV4W9Plx7g==} engines: {node: '>=12'} + '@tanstack/hotkeys@0.3.0': + resolution: {integrity: sha512-y2uawGLj/GrMNDffaaC0YS7tZPwchDbWAKnU/XObjtyQ4oRGVLyg/2PPbT/hWNQ2PbbrolB098NvlYM0OR3XSw==} + engines: {node: '>=18'} + '@tanstack/query-core@5.87.1': resolution: {integrity: sha512-HOFHVvhOCprrWvtccSzc7+RNqpnLlZ5R6lTmngb8aq7b4rc2/jDT0w+vLdQ4lD9bNtQ+/A4GsFXy030Gk4ollA==} '@tanstack/query-devtools@5.87.3': resolution: {integrity: sha512-LkzxzSr2HS1ALHTgDmJH5eGAVsSQiuwz//VhFW5OqNk0OQ+Fsqba0Tsf+NzWRtXYvpgUqwQr4b2zdFZwxHcGvg==} + '@tanstack/react-hotkeys@0.3.0': + resolution: {integrity: sha512-DkSu+8lDwUGzZLk1WVgT6XtfxTa7mFWOGmiDYK+2pe7GqbnuQgUIQPRdo82yCuHYzNoEJgjoYo/HJ8frD4mmcA==} + engines: {node: '>=18'} + peerDependencies: + react: 19.1.0 + react-dom: 19.1.0 + '@tanstack/react-query-devtools@5.87.3': resolution: {integrity: sha512-uV7m4/m58jU4OaLEyiPLRoXnL5H5E598lhFLSXIcK83on+ZXW7aIfiu5kwRwe1qFa4X4thH8wKaxz1lt6jNmAA==} peerDependencies: @@ -9220,6 +9234,12 @@ packages: react: 19.1.0 react-dom: 19.1.0 + '@tanstack/react-store@0.9.1': + resolution: {integrity: sha512-YzJLnRvy5lIEFTLWBAZmcOjK3+2AepnBv/sr6NZmiqJvq7zTQggyK99Gw8fqYdMdHPQWXjz0epFKJXC+9V2xDA==} + peerDependencies: + react: 19.1.0 + react-dom: 19.1.0 + '@tanstack/react-table@8.21.3': resolution: {integrity: sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==} engines: {node: '>=12'} @@ -9293,6 +9313,9 @@ packages: '@tanstack/store@0.7.5': resolution: {integrity: sha512-qd/OjkjaFRKqKU4Yjipaen/EOB9MyEg6Wr9fW103RBPACf1ZcKhbhcu2S5mj5IgdPib6xFIgCUti/mKVkl+fRw==} + '@tanstack/store@0.9.1': + resolution: {integrity: sha512-+qcNkOy0N1qSGsP7omVCW0SDrXtaDcycPqBDE726yryiA5eTDFpjBReaYjghVJwNf1pcPMyzIwTGlYjCSQR0Fg==} + '@tanstack/table-core@8.21.3': resolution: {integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==} engines: {node: '>=12'} @@ -9555,9 +9578,6 @@ packages: '@types/node@20.19.13': resolution: {integrity: sha512-yCAeZl7a0DxgNVteXFHt9+uyFbqXGy/ShC4BlcHkoE0AfGXYv/BUiplV72DjMYXHDBXFjhvr6DD1NiRVfB4j8g==} - '@types/node@20.19.33': - resolution: {integrity: sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==} - '@types/node@22.19.10': resolution: {integrity: sha512-tF5VOugLS/EuDlTBijk0MqABfP8UxgYazTLo3uIn3b4yJgg26QRbVYJYsDtHrjdDUIRfP70+VfhTTc+CE1yskw==} @@ -21858,10 +21878,21 @@ snapshots: '@tanstack/history@1.133.28': {} + '@tanstack/hotkeys@0.3.0': + dependencies: + '@tanstack/store': 0.9.1 + '@tanstack/query-core@5.87.1': {} '@tanstack/query-devtools@5.87.3': {} + '@tanstack/react-hotkeys@0.3.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@tanstack/hotkeys': 0.3.0 + '@tanstack/react-store': 0.9.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + '@tanstack/react-query-devtools@5.87.3(@tanstack/react-query@5.87.1(react@19.1.0))(react@19.1.0)': dependencies: '@tanstack/query-devtools': 5.87.3 @@ -21903,6 +21934,13 @@ snapshots: react-dom: 19.1.0(react@19.1.0) use-sync-external-store: 1.6.0(react@19.1.0) + '@tanstack/react-store@0.9.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@tanstack/store': 0.9.1 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + use-sync-external-store: 1.6.0(react@19.1.0) + '@tanstack/react-table@8.21.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@tanstack/table-core': 8.21.3 @@ -21999,6 +22037,8 @@ snapshots: '@tanstack/store@0.7.5': {} + '@tanstack/store@0.9.1': {} + '@tanstack/table-core@8.21.3': {} '@tanstack/virtual-core@3.13.12': {} @@ -22290,11 +22330,6 @@ snapshots: dependencies: undici-types: 6.21.0 - '@types/node@20.19.33': - dependencies: - undici-types: 6.21.0 - optional: true - '@types/node@22.19.10': dependencies: undici-types: 6.21.0 @@ -24365,7 +24400,7 @@ snapshots: dotenv-expand@11.0.7: dependencies: - dotenv: 16.4.7 + dotenv: 16.6.1 dotenv@16.4.7: {} @@ -26108,7 +26143,7 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 20.19.33 + '@types/node': 22.19.11 merge-stream: 2.0.0 supports-color: 8.1.1 optional: true