("");
+ const onSuccessRef = useRef(onSuccess);
+ onSuccessRef.current = onSuccess;
const organization = useOrganization();
const project = useProject();
const isLoading = fetcher.state !== "idle";
@@ -66,11 +68,11 @@ export function AIGeneratedCronField({ onSuccess }: AIGeneratedCronFieldProps) {
useEffect(() => {
if (resultData?.cron !== undefined) {
- onSuccess(resultData.cron);
+ onSuccessRef.current(resultData.cron);
}
}, [resultData?.cron]);
- const submit = useCallback(async (value: string) => {
+ const submit = (value: string) => {
fetcher.submit(
{ message: value },
{
@@ -79,7 +81,7 @@ export function AIGeneratedCronField({ onSuccess }: AIGeneratedCronFieldProps) {
encType: "application/json",
}
);
- }, []);
+ };
return (
diff --git a/apps/webapp/app/routes/resources.platform-changelogs.tsx b/apps/webapp/app/routes/resources.platform-changelogs.tsx
index ed62de3c1df..17ddcf8d2f2 100644
--- a/apps/webapp/app/routes/resources.platform-changelogs.tsx
+++ b/apps/webapp/app/routes/resources.platform-changelogs.tsx
@@ -2,6 +2,7 @@ import { json } from "@remix-run/node";
import type { LoaderFunctionArgs } from "@remix-run/node";
import { useFetcher, type ShouldRevalidateFunction } from "@remix-run/react";
import { useEffect, useRef } from "react";
+import { useLatest } from "react-use";
import { logger } from "~/services/logger.server";
import { requireUserId } from "~/services/session.server";
import { getRecentChangelogs, verifyOrgMembership } from "~/services/platformNotifications.server";
@@ -42,28 +43,31 @@ const POLL_INTERVAL_MS = 60_000;
export function useRecentChangelogs(organizationId?: string, projectId?: string) {
const fetcher = useFetcher();
+ const { load, state } = fetcher;
+ const stateRef = useLatest(state);
const lastLoadedUrl = useRef(null);
+ const params = new URLSearchParams();
+ if (organizationId) params.set("organizationId", organizationId);
+ if (projectId) params.set("projectId", projectId);
+ const qs = params.toString();
+ const url = `/resources/platform-changelogs${qs ? `?${qs}` : ""}`;
useEffect(() => {
- const params = new URLSearchParams();
- if (organizationId) params.set("organizationId", organizationId);
- if (projectId) params.set("projectId", projectId);
- const qs = params.toString();
- const url = `/resources/platform-changelogs${qs ? `?${qs}` : ""}`;
-
- if (lastLoadedUrl.current !== url && fetcher.state === "idle") {
+ if (lastLoadedUrl.current !== url && state === "idle") {
lastLoadedUrl.current = url;
- fetcher.load(url);
+ load(url);
}
+ }, [load, state, url]);
+ useEffect(() => {
const interval = setInterval(() => {
- if (fetcher.state === "idle") {
- fetcher.load(url);
+ if (stateRef.current === "idle") {
+ load(url);
}
}, POLL_INTERVAL_MS);
return () => clearInterval(interval);
- }, [organizationId, projectId]);
+ }, [load, stateRef, url]);
return {
changelogs: fetcher.data?.changelogs ?? [],
diff --git a/apps/webapp/app/routes/resources.platform-notifications.tsx b/apps/webapp/app/routes/resources.platform-notifications.tsx
index bf3dfe41e44..afa17181a07 100644
--- a/apps/webapp/app/routes/resources.platform-notifications.tsx
+++ b/apps/webapp/app/routes/resources.platform-notifications.tsx
@@ -2,6 +2,7 @@ import { json } from "@remix-run/node";
import type { LoaderFunctionArgs } from "@remix-run/node";
import { useFetcher, type ShouldRevalidateFunction } from "@remix-run/react";
import { useEffect, useRef } from "react";
+import { useLatest } from "react-use";
import { requireUserId } from "~/services/session.server";
import {
getActivePlatformNotifications,
@@ -41,24 +42,27 @@ const POLL_INTERVAL_MS = 60000; // 1 minute
export function usePlatformNotifications(organizationId: string, projectId: string) {
const fetcher = useFetcher();
+ const { load, state } = fetcher;
+ const stateRef = useLatest(state);
const lastLoadedUrl = useRef(null);
+ const url = `/resources/platform-notifications?organizationId=${encodeURIComponent(organizationId)}&projectId=${encodeURIComponent(projectId)}`;
useEffect(() => {
- const url = `/resources/platform-notifications?organizationId=${encodeURIComponent(organizationId)}&projectId=${encodeURIComponent(projectId)}`;
-
- if (lastLoadedUrl.current !== url && fetcher.state === "idle") {
+ if (lastLoadedUrl.current !== url && state === "idle") {
lastLoadedUrl.current = url;
- fetcher.load(url);
+ load(url);
}
+ }, [load, state, url]);
+ useEffect(() => {
const interval = setInterval(() => {
- if (fetcher.state === "idle") {
- fetcher.load(url);
+ if (stateRef.current === "idle") {
+ load(url);
}
}, POLL_INTERVAL_MS);
return () => clearInterval(interval);
- }, [organizationId, projectId]);
+ }, [load, stateRef, url]);
return {
notifications: fetcher.data?.notifications ?? [],
diff --git a/apps/webapp/app/routes/storybook.ai-agent/route.tsx b/apps/webapp/app/routes/storybook.ai-agent/route.tsx
index f69a43c57b6..6220aec5495 100644
--- a/apps/webapp/app/routes/storybook.ai-agent/route.tsx
+++ b/apps/webapp/app/routes/storybook.ai-agent/route.tsx
@@ -795,7 +795,6 @@ function AgentOrb({
colors = AGENT_ORB_PALETTE,
restColor = "#ffffff",
colored = true,
- restShape = "triangle",
dotCount = 21,
orbitCount = 3,
particlesPerOrbit = 3,
@@ -824,7 +823,7 @@ function AgentOrb({
() => buildDotSpecs(effDotCount, orbitCount, effParticles),
[effDotCount, orbitCount, effParticles]
);
- const restPoints = useMemo(() => triangleOutline(effDotCount), [restShape, effDotCount]);
+ const restPoints = useMemo(() => triangleOutline(effDotCount), [effDotCount]);
useEffect(() => {
activeRef.current = active;
@@ -846,7 +845,7 @@ function AgentOrb({
restPoints,
dotSpecs,
orbitGeoms,
- paletteRgb: colors.map(hexToRgb),
+ paletteRgb: colorsKey.split(",").map(hexToRgb),
restRgb: hexToRgb(restColor),
colored,
radiusScale,
@@ -1129,7 +1128,7 @@ function AgentLogoMorph({
outline: logoOutlinePoints(dotCount),
dotSpecs: buildDotSpecs(dotCount, orbitCount, particlesPerOrbit),
orbitGeoms: buildOrbitGeoms(orbitCount),
- paletteRgb: colors.map(hexToRgb),
+ paletteRgb: colorsKey.split(",").map(hexToRgb),
logoRgb: hexToRgb(logoColor),
};
diff --git a/apps/webapp/app/routes/storybook.filter/route.tsx b/apps/webapp/app/routes/storybook.filter/route.tsx
index ed5b65ed005..6658ae69581 100644
--- a/apps/webapp/app/routes/storybook.filter/route.tsx
+++ b/apps/webapp/app/routes/storybook.filter/route.tsx
@@ -149,10 +149,13 @@ const statuses = allTaskRunStatuses.map((status) => ({
function Statuses({ trigger, clearSearchValue, shortcut, searchValue, setFilterType }: MenuProps) {
const { values, replace } = useSearchParams();
- const handleChange = useCallback((values: string[]) => {
- clearSearchValue();
- replace({ status: values });
- }, []);
+ const handleChange = useCallback(
+ (values: string[]) => {
+ clearSearchValue();
+ replace({ status: values });
+ },
+ [clearSearchValue, replace]
+ );
const filtered = useMemo(() => {
return statuses.filter((item) => item.title.toLowerCase().includes(searchValue.toLowerCase()));
@@ -205,10 +208,13 @@ function Environments({
}: MenuProps) {
const { values, replace } = useSearchParams();
- const handleChange = useCallback((values: string[]) => {
- clearSearchValue();
- replace({ environment: values });
- }, []);
+ const handleChange = useCallback(
+ (values: string[]) => {
+ clearSearchValue();
+ replace({ environment: values });
+ },
+ [clearSearchValue, replace]
+ );
const filtered = useMemo(() => {
return environments.filter((item) =>
diff --git a/apps/webapp/app/routes/storybook.select/route.tsx b/apps/webapp/app/routes/storybook.select/route.tsx
index c9ce1495c96..605abca5aca 100644
--- a/apps/webapp/app/routes/storybook.select/route.tsx
+++ b/apps/webapp/app/routes/storybook.select/route.tsx
@@ -1,6 +1,5 @@
import { CircleStackIcon } from "@heroicons/react/20/solid";
import { Form, useNavigate } from "@remix-run/react";
-import { useCallback } from "react";
import { LogoIcon } from "~/components/LogoIcon";
import { Button } from "~/components/primitives/Buttons";
import {
@@ -141,13 +140,13 @@ function Statuses() {
const location = useOptimisticLocation();
const search = new URLSearchParams(location.search);
- const handleChange = useCallback((values: string[]) => {
+ const handleChange = (values: string[]) => {
search.delete("status");
for (const value of values) {
search.append("status", value);
}
navigate(`${location.pathname}?${search.toString()}`, { replace: true });
- }, []);
+ };
return (