diff --git a/frontend/package.json b/frontend/package.json index 9f8f0b451a..035a0291b7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -80,6 +80,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.167.1", diff --git a/frontend/src/app/dev-toolbar.tsx b/frontend/src/app/dev-toolbar.tsx new file mode 100644 index 0000000000..b08acb1526 --- /dev/null +++ b/frontend/src/app/dev-toolbar.tsx @@ -0,0 +1,179 @@ +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 { 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 debugSequence: Hotkey = "Mod+Shift+G"; +const posthogDebugSequence: Hotkey[] = [debugSequence, "R"]; +const posthogStartRecordingSequence: Hotkey[] = [...posthogDebugSequence, "S"]; +const posthogStopRecordingSequence: Hotkey[] = [...posthogDebugSequence, "T"]; +const posthogLoadToolbarSequence: Hotkey[] = [...posthogDebugSequence, "L"]; +const clearCacheSequence: Hotkey[] = [debugSequence, "C"]; +const reportIssueSequence: Hotkey[] = [debugSequence, "I"]; + +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(); + + // recording start + useHotkeySequence(posthogStartRecordingSequence, () => { + posthog.startSessionRecording(); + }); + // recording stop + useHotkeySequence(posthogStopRecordingSequence, () => { + posthog.stopSessionRecording(); + }); + // load toolbar + useHotkeySequence(posthogLoadToolbarSequence, () => { + posthog.toolbar.loadToolbar(); + }); + // clear cache + useHotkeySequence(clearCacheSequence, () => { + queryClient.clear(); + }); + // report issue + useHotkeySequence(reportIssueSequence, () => { + 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 df1f7ef749..188cc80ba6 100644 --- a/frontend/src/routes/__root.tsx +++ b/frontend/src/routes/__root.tsx @@ -1,12 +1,12 @@ import type { Clerk } from "@clerk/clerk-js"; import type { QueryClient } from "@tanstack/react-query"; import { - Outlet, createRootRouteWithContext, + Outlet, useNavigate, } from "@tanstack/react-router"; import { TanStackRouterDevtools } from "@tanstack/react-router-devtools"; -import { type PropsWithChildren, Suspense, lazy } from "react"; +import { lazy, type PropsWithChildren, Suspense } from "react"; import { match } from "ts-pattern"; import type { CloudContext, @@ -16,6 +16,7 @@ import type { OrganizationContext, ProjectContext, } from "@/app/data-providers/cache"; +import { DevToolbar } from "@/app/dev-toolbar"; import { FullscreenLoading } from "@/components"; import { clerkPromise } from "@/lib/auth"; import { cloudEnv } from "@/lib/env"; @@ -24,6 +25,7 @@ function RootRoute() { return ( <> + {import.meta.env.DEV ? ( ) : null} @@ -37,15 +39,24 @@ const LazyClerkProvider = lazy(() => import("@clerk/themes"), clerkPromise, ]).then(([{ ClerkProvider }, { dark }, clerk]) => ({ - default: ({ children, navigatePush, navigateReplace }: PropsWithChildren<{ navigatePush: (to: string) => void; navigateReplace: (to: string) => void }>) => ( + default: ({ + children, + navigatePush, + navigateReplace, + }: PropsWithChildren<{ + navigatePush: (to: string) => void; + navigateReplace: (to: string) => void; + }>) => ( navigate({ to, replace: true })} > + {import.meta.env.DEV ? ( ) : null} diff --git a/package.json b/package.json index 951f504a7c..0f9513b585 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "@rivetkit/engine-cli": "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 0b42b865e3..78dc48c40e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,7 +15,7 @@ overrides: '@rivetkit/engine-cli': 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@413534c @@ -1388,22 +1388,6 @@ importers: specifier: ^3.1.1 version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.10)(@vitest/ui@3.1.1)(less@4.4.1)(lightningcss@1.32.0)(msw@2.12.10(@types/node@22.19.10)(typescript@5.9.3))(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0) - examples/global-smoke-test: - dependencies: - rivetkit: - specifier: workspace:* - version: link:../../rivetkit-typescript/packages/rivetkit - devDependencies: - '@types/node': - specifier: ^22.13.9 - version: 22.19.15 - tsx: - specifier: ^4.7.1 - version: 4.21.0 - typescript: - specifier: ^5.5.2 - version: 5.9.3 - examples/hello-world: dependencies: '@rivetkit/react': @@ -3407,7 +3391,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 @@ -3565,6 +3549,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) @@ -10764,12 +10751,23 @@ packages: resolution: {integrity: sha512-g9rWt2rNC0Ztga4LAFWfBDT0VREij5mxBo+oH5+cQ5Q46HVIhtJNty/UU5wPzbTKMHJHGNJOWgrlEfcQ4GgjEA==} engines: {node: '>=20.19'} + '@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: @@ -10806,6 +10804,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-store@0.9.2': resolution: {integrity: sha512-Vt5usJE5sHG/cMechQfmwvwne6ktGCELe89Lmvoxe3LKRoFrhPa8OCKWs0NliG8HTJElEIj7PLtaBQIcux5pAQ==} peerDependencies: @@ -10883,6 +10887,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/store@0.9.2': resolution: {integrity: sha512-K013lUJEFJK2ofFQ/hZKJUmCnpcV00ebLyOyFOWQvyQHUOZp/iYO84BM6aOGiV81JzwbX0APTVmW8YI7yiG5oA==} @@ -27033,10 +27040,21 @@ snapshots: '@tanstack/history@1.161.5': {} + '@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 @@ -27077,6 +27095,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-store@0.9.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@tanstack/store': 0.9.2 @@ -27179,6 +27204,8 @@ snapshots: '@tanstack/store@0.7.5': {} + '@tanstack/store@0.9.1': {} + '@tanstack/store@0.9.2': {} '@tanstack/table-core@8.21.3': {}