diff --git a/CHANGELOG.md b/CHANGELOG.md index 74d6e5e5a..0a0af6bf1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fixed line numbers being selectable in Safari in the lightweight code highlighter. [#1037](https://github.com/sourcebot-dev/sourcebot/pull/1037) - Fixed GitLab sync deleting repos when the API returns a non-404 error (e.g. 500) during group/user/project fetch. [#1039](https://github.com/sourcebot-dev/sourcebot/pull/1039) +- Fixed React hydration mismatch in `KeyboardShortcutHint` caused by platform detection running at module load time during SSR. [#1041](https://github.com/sourcebot-dev/sourcebot/pull/1041) ### Added - Added optional copy button to the lightweight code highlighter (`isCopyButtonVisible` prop), shown on hover. [#1037](https://github.com/sourcebot-dev/sourcebot/pull/1037) diff --git a/packages/web/src/app/components/keyboardShortcutHint.tsx b/packages/web/src/app/components/keyboardShortcutHint.tsx index ebb5d082e..5f8e6c4ac 100644 --- a/packages/web/src/app/components/keyboardShortcutHint.tsx +++ b/packages/web/src/app/components/keyboardShortcutHint.tsx @@ -1,7 +1,8 @@ 'use client'; -import { cn, IS_MAC } from '@/lib/utils' -import React, { useMemo } from 'react' +import { cn } from '@/lib/utils' +import { useIsMac } from '@/hooks/useIsMac' +import { useMemo } from 'react' interface KeyboardShortcutHintProps { shortcut: string @@ -81,16 +82,17 @@ function mapKey(key: string, keyMap: Record): string { * Converts react-hotkeys syntax to platform-appropriate keyboard shortcut display. * Accepts formats like: "mod+b", "alt+shift+f12", "ctrl enter" */ -function getPlatformShortcut(shortcut: string): string { +function getPlatformShortcut(shortcut: string, isMac: boolean): string { // Split by + or space to handle both "mod+b" and "⌘ B" formats const keys = shortcut.split(/[+\s]+/).filter(Boolean); - const keyMap = IS_MAC ? MAC_KEY_MAP : WINDOWS_KEY_MAP; - + const keyMap = isMac ? MAC_KEY_MAP : WINDOWS_KEY_MAP; + return keys.map(key => mapKey(key, keyMap)).join(' '); } export function KeyboardShortcutHint({ shortcut, label, className }: KeyboardShortcutHintProps) { - const platformShortcut = useMemo(() => getPlatformShortcut(shortcut), [shortcut]); + const isMac = useIsMac(); + const platformShortcut = useMemo(() => getPlatformShortcut(shortcut, isMac), [shortcut, isMac]); return (
diff --git a/packages/web/src/features/chat/components/chatBox/chatBox.tsx b/packages/web/src/features/chat/components/chatBox/chatBox.tsx index 32602c75c..f25ed311d 100644 --- a/packages/web/src/features/chat/components/chatBox/chatBox.tsx +++ b/packages/web/src/features/chat/components/chatBox/chatBox.tsx @@ -5,7 +5,8 @@ import { Button } from "@/components/ui/button"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { CustomEditor, LanguageModelInfo, MentionElement, RenderElementPropsFor, SearchScope } from "@/features/chat/types"; import { insertMention, slateContentToString } from "@/features/chat/utils"; -import { cn, IS_MAC } from "@/lib/utils"; +import { cn } from "@/lib/utils"; +import { useIsMac } from "@/hooks/useIsMac"; import { computePosition, flip, offset, shift, VirtualElement } from "@floating-ui/react"; import { ArrowUp, Loader2, StopCircleIcon } from "lucide-react"; import { Fragment, KeyboardEvent, memo, useCallback, useEffect, useMemo, useRef, useState } from "react"; @@ -367,6 +368,7 @@ const MentionComponent = ({ }: RenderElementPropsFor) => { const selected = useSelected(); const focused = useFocused(); + const isMac = useIsMac(); if (data.type === 'file') { return ( @@ -384,7 +386,7 @@ const MentionComponent = ({ > {/* @see: https://github.com/ianstormtaylor/slate/issues/3490 */} - {IS_MAC ? ( + {isMac ? ( {children} diff --git a/packages/web/src/hooks/useIsMac.ts b/packages/web/src/hooks/useIsMac.ts new file mode 100644 index 000000000..075d97670 --- /dev/null +++ b/packages/web/src/hooks/useIsMac.ts @@ -0,0 +1,13 @@ +'use client'; + +import { useState, useEffect } from 'react'; + +export function useIsMac(): boolean { + const [isMac, setIsMac] = useState(false); + + useEffect(() => { + setIsMac(/Mac OS X/.test(navigator.userAgent)); + }, []); + + return isMac; +} diff --git a/packages/web/src/lib/utils.ts b/packages/web/src/lib/utils.ts index d61832326..43a1ad3ae 100644 --- a/packages/web/src/lib/utils.ts +++ b/packages/web/src/lib/utils.ts @@ -619,8 +619,6 @@ export const getOrgMetadata = (org: Org): OrgMetadata | null => { return currentMetadata.success ? currentMetadata.data : null; } -export const IS_MAC = typeof navigator !== 'undefined' && /Mac OS X/.test(navigator.userAgent); - export const isHttpError = (error: unknown, status: number): boolean => { return error !== null