diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 9a45ba7..51ef7b2 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -26,8 +26,12 @@ module.exports = { react: { version: "detect" }, }, parser: "@typescript-eslint/parser", - plugins: ["react-refresh"], + plugins: [ + "react-refresh", + "eslint-plugin-react-compiler", + ], rules: { + 'react-compiler/react-compiler': "error", // "@typescript-eslint/naming-convention": "error", // only enforce for object properties // The `@typescript-eslint/naming-convention` rule allows `leadingUnderscore` and `trailingUnderscore` settings. However, the existing `no-underscore-dangle` rule already takes care of this. diff --git a/bun.lockb b/bun.lockb index 732d427..6f0019c 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 9c77de9..b70c63f 100644 --- a/package.json +++ b/package.json @@ -11,11 +11,15 @@ "renovate": "bunx npm-check-updates -i -x eslint", "preview": "NODE_ENV=production bun src/hono.ts" }, + "overrides": { + "@types/react": "npm:types-react@rc", + "@types/react-dom": "npm:types-react-dom@rc" + }, "dependencies": { "@ctrl/golang-template": "^1.4.1", - "@hattip/core": "^0.0.47", - "@hattip/router": "^0.0.47", - "@hattip/vite": "^0.0.47", + "@hattip/core": "^0.0.48", + "@hattip/router": "^0.0.48", + "@hattip/vite": "^0.0.48", "@hookform/error-message": "^2.0.1", "@hookform/resolvers": "^3.9.0", "@la55u/react-spring-bottom-sheet-updated": "^1.0.3", @@ -26,10 +30,10 @@ "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-switch": "^1.1.0", "@radix-ui/react-tooltip": "^1.1.2", - "@tanstack/react-query": "^5.52.1", - "@tanstack/react-router": "^1.50.1", - "@tanstack/router-devtools": "^1.50.1", - "@tanstack/router-vite-plugin": "^1.49.3", + "@tanstack/react-query": "^5.55.4", + "@tanstack/react-router": "^1.57.9", + "@tanstack/router-devtools": "^1.57.9", + "@tanstack/router-vite-plugin": "^1.57.9", "@trpc/client": "^11.0.0-rc.502", "@trpc/react-query": "^11.0.0-rc.364", "@trpc/server": "^11.0.0-rc.364", @@ -39,24 +43,26 @@ "@uidotdev/usehooks": "^2.4.1", "acebase": "^1.29.5", "autoprefixer": "^10.4.20", + "babel-plugin-react-compiler": "^0.0.0-experimental-fe484b5-20240911", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "color": "^4.2.3", "date-fns": "^3.6.0", "dotenv": "^16.4.5", - "eslint-plugin-react": "^7.35.0", + "eslint-plugin-react": "^7.35.2", + "eslint-plugin-react-compiler": "^0.0.0-experimental-5c9a529-20240911", "eventsource-parser": "^2.0.1", - "framer-motion": "11.3.30", - "hono": "^4.5.9", + "framer-motion": "12.0.0-alpha.1", + "hono": "^4.6.1", "jotai": "^2.9.3", "lodash": "^4.17.21", - "lucide-react": "^0.436.0", + "lucide-react": "^0.439.0", "markdown-to-jsx": "^7.5.0", - "ollama": "^0.5.8", - "openai": "^4.56.0", + "ollama": "^0.5.9", + "openai": "^4.59.0", "qrcode": "^1.5.4", - "react": "18.3.1", - "react-dom": "18.3.1", + "react": "^19.0.0-rc-d6cb4e77-20240911", + "react-dom": "^19.0.0-rc-d6cb4e77-20240911", "react-error-boundary": "^4.0.13", "react-hook-form": "^7.53.0", "react-markdown": "^9.0.1", @@ -73,36 +79,36 @@ "superjson": "^2.2.1", "tailwind-merge": "^2.5.2", "tailwindcss-animate": "^1.0.7", - "tsx": "^4.18.0", + "tsx": "^4.19.0", "usehooks-ts": "^3.1.0", - "vinxi": "^0.4.1", - "virtua": "^0.33.7", - "vite-plugin-pwa": "^0.20.2", + "vinxi": "^0.4.2", + "virtua": "^0.34.2", + "vite-plugin-pwa": "^0.20.5", "vite-tsconfig-paths": "^5.0.1", "web-push": "^3.6.7", "zod": "^3.23.8" }, "devDependencies": { - "@types/bun": "^1.1.8", + "@types/bun": "^1.1.9", "@types/eslint": "^9.6.1", - "@types/node": "^22.5.0", + "@types/node": "^22.5.4", "@types/qrcode": "^1.5.5", - "@types/react": "^18.3.4", - "@types/react-dom": "^18.3.0", + "@types/react": "npm:types-react@rc", + "@types/react-dom": "npm:types-react-dom@rc", "@types/react-scroll-to-bottom": "^4.2.5", "@types/web-push": "^3.6.3", - "@typescript-eslint/eslint-plugin": "^8.2.0", - "@typescript-eslint/parser": "^8.2.0", + "@typescript-eslint/eslint-plugin": "^8.5.0", + "@typescript-eslint/parser": "^8.5.0", "@vitejs/plugin-react": "^4.3.1", "eslint": "^8.57.0", "eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-refresh": "^0.4.11", - "postcss": "^8.4.41", + "postcss": "^8.4.45", "prettier": "^3.3.3", "prettier-plugin-tailwindcss": "^0.6.6", "tailwindcss": "^3.4.10", - "typescript": "^5.5.4", - "vite": "^5.4.2", + "typescript": "^5.6.2", + "vite": "^5.4.4", "workbox-core": "^7.1.0", "workbox-precaching": "^7.1.0", "workbox-window": "^7.1.0" diff --git a/src/components/primitives/Input.tsx b/src/components/primitives/Input.tsx index 2fd733c..8d1f3aa 100644 --- a/src/components/primitives/Input.tsx +++ b/src/components/primitives/Input.tsx @@ -38,13 +38,7 @@ type GenerativeOpts = { }; const InputComponent = forwardRef((props, ref) => { - let context: ReturnType | undefined; - try { - // eslint-disable-next-line react-hooks/rules-of-hooks - context = useFormContext(); - } catch (e) { - /** We allow using inputs without form context */ - } + const context = useFormContext(); const hasText = props.value !== undefined && props.value !== ""; const aiGenerateMutation = api.ai.generate.useMutation({ @@ -190,4 +184,4 @@ const InputComponent = forwardRef((props, ref) => ); }); -export const Input = motion(InputComponent); +export const Input = motion.create(InputComponent); diff --git a/src/hooks/usePrevious.tsx b/src/hooks/usePrevious.tsx index 432da48..ff7e515 100644 --- a/src/hooks/usePrevious.tsx +++ b/src/hooks/usePrevious.tsx @@ -9,12 +9,15 @@ export function usePrevious(value: T) { prev: undefined, }); + // eslint-disable-next-line react-compiler/react-compiler const current = ref.current.value; if (value !== current) { + // eslint-disable-next-line react-compiler/react-compiler ref.current = { value, prev: current, }; } + // eslint-disable-next-line react-compiler/react-compiler return ref.current.prev; } diff --git a/src/hooks/useSpeechRecognition.tsx b/src/hooks/useSpeechRecognition.tsx index e2d7539..17534d4 100644 --- a/src/hooks/useSpeechRecognition.tsx +++ b/src/hooks/useSpeechRecognition.tsx @@ -161,6 +161,7 @@ export const useSpeechRecognition = () => { startListening, stopListening, timeoutProgress, + // eslint-disable-next-line react-compiler/react-compiler finalTranscript: finalTranscriptRef.current, resetInterimTranscript, resetFinalTranscript, diff --git a/src/hooks/useTouchHold.tsx b/src/hooks/useTouchHold.tsx index 601473c..8833ad6 100644 --- a/src/hooks/useTouchHold.tsx +++ b/src/hooks/useTouchHold.tsx @@ -203,9 +203,13 @@ export function useTouchHold({ }, [handleTouchMove, reset]); return { + // eslint-disable-next-line react-compiler/react-compiler onTouchStart: handleTouchStart as unknown as React.TouchEventHandler, + // eslint-disable-next-line react-compiler/react-compiler onMouseDown: handleTouchStart as unknown as React.MouseEventHandler, + // eslint-disable-next-line react-compiler/react-compiler onTouchEnd: handleTouchEnd as React.TouchEventHandler, + // eslint-disable-next-line react-compiler/react-compiler onMouseUp: handleTouchEnd as React.MouseEventHandler, onMouseLeave: reset, onContextMenu: onContextMenu, diff --git a/src/internal/AnimatedOutlet.tsx b/src/internal/AnimatedOutlet.tsx index 6664bc3..a157a0f 100644 --- a/src/internal/AnimatedOutlet.tsx +++ b/src/internal/AnimatedOutlet.tsx @@ -1,19 +1,5 @@ import { getRouterContext, Outlet, useMatches } from "@tanstack/react-router"; -import { - anticipate, - backIn, - backInOut, - backOut, - circIn, - circInOut, - circOut, - easeIn, - easeOut, - motion, - MotionConfig, - MotionProps, - useIsPresent, -} from "framer-motion"; +import { motion, MotionProps, useIsPresent } from "framer-motion"; import cloneDeep from "lodash/cloneDeep"; import { forwardRef, useContext, useRef } from "react"; import { AnimatedOutletProps, RouteTransitionVariants } from "./AnimatedOutlet.types"; @@ -41,14 +27,17 @@ const AnimatedOutlet = forwardRef(({ direct let renderedContext = routerContext; if (isPresent) { + // eslint-disable-next-line react-compiler/react-compiler prevMatches.current = cloneDeep(matches); } else { renderedContext = cloneDeep(routerContext); renderedContext.__store.state.matches = [ + // eslint-disable-next-line react-compiler/react-compiler ...matches.map((match, i) => ({ ...(prevMatches.current[i] ?? match), id: match.id, })), + // eslint-disable-next-line react-compiler/react-compiler ...prevMatches.current.slice(matches.length), ]; } diff --git a/src/layouts/ChaiMessage/components/ChatBubble.tsx b/src/layouts/ChaiMessage/components/ChatBubble.tsx index bfd7528..5c5994b 100644 --- a/src/layouts/ChaiMessage/components/ChatBubble.tsx +++ b/src/layouts/ChaiMessage/components/ChatBubble.tsx @@ -163,8 +163,7 @@ export const ChatBubble = React.memo( setIsDraggingToRevealTime(Math.abs(info.offset.x)); } }, - // eslint-disable-next-line react-hooks/exhaustive-deps -- don't need dragX in deps - [setIsDraggingToRevealTime], + [dragX, setIsDraggingToRevealTime], ); const handleDragEnd = useCallback( diff --git a/src/layouts/ChaiMessage/components/ChatInput.tsx b/src/layouts/ChaiMessage/components/ChatInput.tsx index 2b59b84..9066fb0 100644 --- a/src/layouts/ChaiMessage/components/ChatInput.tsx +++ b/src/layouts/ChaiMessage/components/ChatInput.tsx @@ -42,7 +42,10 @@ export const ChatInput = React.memo(({ onMessageSend, className }: ChatInputProp onChange={(e) => setMessage(e.target.value)} onKeyDown={(e) => { if (window.innerWidth < 768) return; - e.key === "Enter" && !e.shiftKey && void sendMessage(e); + if (e.key === "Enter" && !e.shiftKey) { + e.preventDefault(); + void sendMessage(e); + } }} /> diff --git a/src/layouts/ChaiMessage/components/ExpandingTextarea.tsx b/src/layouts/ChaiMessage/components/ExpandingTextarea.tsx index eed3a9c..44e6b83 100644 --- a/src/layouts/ChaiMessage/components/ExpandingTextarea.tsx +++ b/src/layouts/ChaiMessage/components/ExpandingTextarea.tsx @@ -22,8 +22,7 @@ export const ExpandingTextarea = forwardRef debounce(resizeTextarea, 100), [resizeTextarea]); useLayoutEffect(() => { resizeTextarea(); @@ -39,7 +38,7 @@ export const ExpandingTextarea = forwardRef { - debouncedResizeTextarea(); + debouncedResizeTextarea()(); props.onInput?.(e); }} /> diff --git a/src/layouts/ChaiMessage/components/Padded.tsx b/src/layouts/ChaiMessage/components/Padded.tsx index 728ba8b..5afbddb 100644 --- a/src/layouts/ChaiMessage/components/Padded.tsx +++ b/src/layouts/ChaiMessage/components/Padded.tsx @@ -2,7 +2,7 @@ import { motion } from "framer-motion"; import { forwardRef, ReactNode } from "react"; import { twMerge } from "tailwind-merge"; -export const Padded = motion( +export const Padded = motion.create( forwardRef((props, ref) => { return (
diff --git a/src/layouts/ChaiMessage/components/VideoCallModal.tsx b/src/layouts/ChaiMessage/components/VideoCallModal.tsx index c25b8b4..1768c01 100644 --- a/src/layouts/ChaiMessage/components/VideoCallModal.tsx +++ b/src/layouts/ChaiMessage/components/VideoCallModal.tsx @@ -269,7 +269,6 @@ export const VideoCallModal = ({ isOpen, onClose, chatId }: VideoCallModalProps) startListening(); void setupAudioAnalyser(); } else { - console.log("Closing video call modal"); cleanupAll(); } diff --git a/src/layouts/ChaiMessage/screens/Texting.tsx b/src/layouts/ChaiMessage/screens/Texting.tsx index 5cf5a59..6d8e50d 100644 --- a/src/layouts/ChaiMessage/screens/Texting.tsx +++ b/src/layouts/ChaiMessage/screens/Texting.tsx @@ -100,7 +100,12 @@ export const Texting = React.memo( /** * On load, scroll to the bottom of the chat */ - const { JumpToBottomButton, jumpToBottom } = useAutoScroll({ scrollerRef, messages, scrollContainerRef }); + // eslint-disable-next-line react-compiler/react-compiler -- https://github.com/facebook/react/issues/30745#issuecomment-2329039708 + const { JumpToBottomButton, jumpToBottom } = useAutoScroll({ + scrollerRef: scrollerRef as React.RefObject, + messages, + scrollContainerRef: scrollContainerRef as React.RefObject, + }); const onBeforeMessageSend = (...props: Parameters) => { jumpToBottom(); diff --git a/vite.config.ts b/vite.config.ts index ba7e637..f5797ab 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -22,7 +22,11 @@ export default createApp({ handler: "./index.html", plugins: () => [ tsconfigPaths(), - react(), + react({ + babel: { + plugins: [["babel-plugin-react-compiler", {}]], + }, + }), VitePWA({ base: "/", srcDir: "src/service-worker",