[Haik]: fix case-sensitive Button import paths across 7 adapter files, replace React use() with useContext in CodeDiff for broader compat, remove unused useResolvedTheme/getSystemTheme/getDocumentTheme from CodeBlockRoot helpers, fix useFileSrc object URL race condition with revocation flag and queueMicrotask, replace OptionList useEffect-based activeIndex with useMemo derivation, swap process.env NODE_ENV check for import.meta.env.DEV, and wrap ElementSelectionProvider ref assignment in useEffect to avoid render-phase side effects

This commit is contained in:
haikdc
2026-04-18 17:48:12 -07:00
parent d2004c2933
commit 89d81dd0aa
16 changed files with 38 additions and 86 deletions
@@ -6,7 +6,7 @@ import {
AuiIf,
} from '@assistant-ui/react';
import { ArrowDownIcon } from 'lucide-react';
import { Button } from '@/app/pages/AgentChat/_shared/button';
import { Button } from '@/app/pages/AgentChat/_shared/Button';
import { TooltipProvider } from './components/tooltip';
import { TooltipIconButton } from './components/TooltipIconButton';
import { UserMessage } from './components/UserMessage/UserMessage';
@@ -8,7 +8,7 @@ import {
TooltipContent,
TooltipTrigger,
} from "./tooltip";
import { Button } from "@/app/pages/AgentChat/_shared/button";
import { Button } from "@/app/pages/AgentChat/_shared/Button";
import { cn } from "@/lib/utils";
type TooltipIconButtonProps = ComponentPropsWithRef<typeof Button> & {
@@ -81,7 +81,7 @@ function useOriginalMessage(): AgentMessage | undefined {
try {
messageId = aui.message().getState().id;
} catch {
return undefined;
// intentionally empty — messageId stays undefined
}
return useAppSelector((state) => {
@@ -29,19 +29,23 @@ const useFileSrc = (file: File | undefined) => {
useEffect(() => {
if (!file) {
setSrc(undefined);
return;
}
let revoked = false;
const objectUrl = URL.createObjectURL(file);
setSrc(objectUrl);
queueMicrotask(() => {
if (!revoked) setSrc(objectUrl);
});
return () => {
revoked = true;
URL.revokeObjectURL(objectUrl);
queueMicrotask(() => setSrc(undefined));
};
}, [file]);
return src;
return file ? src : undefined;
};
const useAttachmentSrc = () => {
@@ -3,7 +3,7 @@
import type { Action } from "../../utils/types";
import { useActionButtons } from "./useActionButtons";
import { cn } from "@/lib/utils";
import { Button } from "@/app/pages/AgentChat/_shared/button";
import { Button } from "@/app/pages/AgentChat/_shared/Button";
interface ActionButtonsProps {
actions: Action[];
@@ -4,7 +4,6 @@ import {
useMemo,
useState,
useCallback,
useEffect,
useRef,
Fragment,
} from "react";
@@ -229,7 +228,7 @@ export function OptionList({
onBeforeAction,
className,
}: OptionListProps) {
if (process.env["NODE_ENV"] !== "production") {
if (import.meta.env.DEV) {
if (value !== undefined && defaultValue !== undefined) {
console.warn(
"[OptionList] Both `value` (controlled) and `defaultValue` (uncontrolled) were provided. `defaultValue` is ignored when `value` is set.",
@@ -297,7 +296,7 @@ export function OptionList({
]);
const optionRefs = useRef<Array<HTMLButtonElement | null>>([]);
const [activeIndex, setActiveIndex] = useState(() => {
const [rawActiveIndex, setActiveIndex] = useState(() => {
const firstSelected = optionStates.findIndex(
(s) => s.isSelected && !s.isDisabled,
);
@@ -306,20 +305,18 @@ export function OptionList({
return firstEnabled >= 0 ? firstEnabled : 0;
});
useEffect(() => {
if (optionStates.length === 0) return;
setActiveIndex((prev) => {
if (
prev < 0 ||
prev >= optionStates.length ||
optionStates[prev].isDisabled
) {
const firstEnabled = optionStates.findIndex((s) => !s.isDisabled);
return firstEnabled >= 0 ? firstEnabled : 0;
}
return prev;
});
}, [optionStates]);
const activeIndex = useMemo(() => {
if (
optionStates.length === 0 ||
(rawActiveIndex >= 0 &&
rawActiveIndex < optionStates.length &&
!optionStates[rawActiveIndex].isDisabled)
) {
return rawActiveIndex;
}
const firstEnabled = optionStates.findIndex((s) => !s.isDisabled);
return firstEnabled >= 0 ? firstEnabled : 0;
}, [rawActiveIndex, optionStates]);
const updateSelection = useCallback(
(next: Set<string>) => {
@@ -1,3 +1,3 @@
export { cn } from "@/lib/utils";
export { Button } from "@/app/pages/AgentChat/_shared/button";
export { Button } from "@/app/pages/AgentChat/_shared/Button";
export { Separator } from "../../separator";
@@ -1,4 +1,4 @@
import type { Action, ActionsConfig } from "./schema";
import type { Action, ActionsConfig } from "@/app/pages/AgentChat/toolkit/approvalToolkit/utils/types";
export type ActionsProp = ActionsConfig | Action[];
@@ -1,3 +1,3 @@
export { cn } from "@/lib/utils";
export { Button } from "@/app/pages/AgentChat/_shared/button";
export { Button } from "@/app/pages/AgentChat/_shared/Button";
export { Separator } from "../../separator";
@@ -1,3 +1,3 @@
export { cn } from "@/lib/utils";
export { Button } from "@/app/pages/AgentChat/_shared/button";
export { Button } from "@/app/pages/AgentChat/_shared/Button";
export { Collapsible, CollapsibleTrigger } from "@/app/pages/AgentChat/_shared/collapsible";
@@ -1,9 +1,5 @@
"use client";
import {
useState,
useEffect,
} from "react";
import { createHighlighter, type Highlighter } from "shiki/bundle/web";
import { createJavaScriptRegexEngine } from "shiki/engine/javascript";
import pierreDarkTheme from "../../../_shared/pierre-dark-theme.js";
@@ -57,52 +53,4 @@ export function setCachedHtml(cacheKey: string, html: string): void {
}
htmlCache.set(cacheKey, html);
}
export function getSystemTheme(): "light" | "dark" {
if (typeof window === "undefined") return "light";
return window.matchMedia?.("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
}
export function getDocumentTheme(): "light" | "dark" | null {
if (typeof document === "undefined") return null;
const root = document.documentElement;
const dataTheme = root.getAttribute("data-theme")?.toLowerCase();
if (dataTheme === "dark") return "dark";
if (dataTheme === "light") return "light";
if (root.classList.contains("dark")) return "dark";
if (root.classList.contains("light")) return "light";
return null;
}
export function useResolvedTheme(): "light" | "dark" {
const [theme, setTheme] = useState<"light" | "dark">(() => {
return getDocumentTheme() ?? getSystemTheme();
});
useEffect(() => {
if (typeof window === "undefined" || typeof document === "undefined") {
return;
}
const update = () => setTheme(getDocumentTheme() ?? getSystemTheme());
const mql = window.matchMedia?.("(prefers-color-scheme: dark)");
mql?.addEventListener("change", update);
const observer = new MutationObserver(update);
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ["class", "data-theme"],
});
return () => {
mql?.removeEventListener("change", update);
observer.disconnect();
};
}, []);
return theme;
}
@@ -6,7 +6,7 @@ import {
useEffect,
useMemo,
createContext,
use,
useContext,
type ReactNode,
} from "react";
import {
@@ -137,7 +137,7 @@ type CodeDiffSharedState = {
const CodeDiffContext = createContext<CodeDiffSharedState | null>(null);
function useCodeDiff(): CodeDiffSharedState {
const context = use(CodeDiffContext);
const context = useContext(CodeDiffContext);
if (!context) {
throw new Error(
"CodeDiff subcomponents must be used within <CodeDiff.Root>.",
@@ -1,3 +1,3 @@
export { cn } from "@/lib/utils";
export { Button } from "@/app/pages/AgentChat/_shared/button";
export { Button } from "@/app/pages/AgentChat/_shared/Button";
export { Collapsible, CollapsibleTrigger } from "@/app/pages/AgentChat/_shared/collapsible";
@@ -1,3 +1,3 @@
export { cn } from "@/lib/utils";
export { Button } from "@/app/pages/AgentChat/_shared/button";
export { Button } from "@/app/pages/AgentChat/_shared/Button";
export { Collapsible, CollapsibleTrigger } from "@/app/pages/AgentChat/_shared/collapsible";
@@ -1,4 +1,4 @@
import React, { useState, useRef, useCallback, useMemo } from 'react';
import React, { useState, useRef, useCallback, useMemo, useEffect } from 'react';
import { type SelectedElement } from './SelectedElement';
import { ElementSelectionContext } from './useElementSelection';
@@ -10,7 +10,9 @@ export const ElementSelectionProvider: React.FC<{ children: React.ReactNode }> =
const iframeRef = useRef<HTMLIFrameElement | null>(null);
const activeOwnerIdRef = useRef(activeOwnerId);
activeOwnerIdRef.current = activeOwnerId;
useEffect(() => {
activeOwnerIdRef.current = activeOwnerId;
}, [activeOwnerId]);
const selectedElements = useMemo(
() => (activeOwnerId ? elementsByOwner[activeOwnerId] ?? [] : []),
+1
View File
@@ -0,0 +1 @@
/// <reference types="vite/client" />