diff --git a/frontend/src/agents/AgentsList.tsx b/frontend/src/agents/AgentsList.tsx
index 4288ec21..079d7a56 100644
--- a/frontend/src/agents/AgentsList.tsx
+++ b/frontend/src/agents/AgentsList.tsx
@@ -400,6 +400,19 @@ function AgentSection({
onCreateFolder(name, currentFolderId || undefined);
};
+ // Must stay above the empty-state returns below: a hook after an early
+ // return is skipped on the render that takes it, which React rejects with
+ // "rendered fewer hooks than expected". Reachable now that each filter is
+ // its own route — landing straight on an empty one renders once while the
+ // data loads, then again once it arrives empty.
+ const breadcrumbItems = useMemo(() => {
+ if (!folders || folderPath.length === 0) return [];
+ return folderPath.map((folderId) => {
+ const folder = folders.find((f) => f.id === folderId);
+ return { id: folderId, name: folder?.name || '' };
+ });
+ }, [folders, folderPath]);
+
const hasNoAgentsAtAll = !isLoading && totalAgents === 0;
const isSearchingWithNoResults =
!isLoading && searchQuery && filteredAgents.length === 0 && totalAgents > 0;
@@ -433,15 +446,6 @@ function AgentSection({
);
}
- // Build breadcrumb items from folder path
- const breadcrumbItems = useMemo(() => {
- if (!folders || folderPath.length === 0) return [];
- return folderPath.map((folderId) => {
- const folder = folders.find((f) => f.id === folderId);
- return { id: folderId, name: folder?.name || '' };
- });
- }, [folders, folderPath]);
-
return (
diff --git a/frontend/src/index.css b/frontend/src/index.css
index d3bd0ffc..a9e6438d 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -690,8 +690,11 @@ Avoid over-scrolling in mobile browsers
font-family: var(--font-mono);
}
+ /* Disclosure content, e.g. an expanded tool call. Short on purpose: the
+ panel's height appears at once, so a long fade reads as the content
+ lagging behind the layout rather than as a reveal. */
.fade-in {
- animation: fadeIn 0.5s ease-in-out;
+ animation: fadeIn 0.16s ease-out;
}
@keyframes fadeIn {
@@ -703,10 +706,13 @@ Avoid over-scrolling in mobile browsers
}
}
+ /* The start state lives in the keyframes, not here: with `opacity: 0` on
+ the element itself the content was only ever visible *because* an
+ animation had run to completion, so anything that stopped it running —
+ a cancelled animation, a reduced-motion reset — left the answer blank.
+ The element now rests visible and the animation is purely additive. */
.fade-in-bubble {
- opacity: 0;
- transform: translateY(10px);
- animation: fadeInUp 0.5s forwards;
+ animation: fadeInUp 0.26s ease-out;
}
.shimmer-text {
@@ -754,9 +760,19 @@ Avoid over-scrolling in mobile browsers
.dark .shimmer-text {
background-image: none;
}
+
+ /* Safe to drop outright now that both rest in their final state. */
+ .fade-in,
+ .fade-in-bubble {
+ animation: none;
+ }
}
@keyframes fadeInUp {
+ from {
+ opacity: 0;
+ transform: translateY(6px);
+ }
to {
opacity: 1;
transform: translateY(0);