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