@@ -473,7 +475,7 @@ export function MessagesView({
>
{viewMode === "timeline" && timelineNodes
- ? timelineNodes.map((node) =>
+ ? timelineNodes.map((node, i) =>
node.type === "date" ? (
),
)
- : display.map((m) => (
+ : display.map((m, i) => (
@@ -879,10 +883,12 @@ function MessageRow({
m,
selected,
onSelect,
+ index = 0,
}: {
m: MessageRecord;
selected: string | null;
onSelect: (id: string) => void;
+ index?: number;
}) {
const meta = parseMeta(m.metadata);
const attachments = meta?.attachments ?? [];
@@ -907,11 +913,12 @@ function MessageRow({
key={m.id}
type="button"
onClick={() => onSelect(m.id)}
- className={`msg-feed-card flex w-full items-start gap-3 rounded-[8px] border p-2.5 text-left transition-all ${
+ className={`msg-feed-card animate-stagger flex w-full items-start gap-3 rounded-[8px] border p-2.5 text-left transition-all ${
selected === m.id
? "border-signal/50 bg-signal/10 shadow-xs"
: "border-hairline bg-surface-2 hover:border-hairline-focus hover:bg-surface"
}`}
+ style={staggerDelay(index)}
>
diff --git a/services/frontend/src/app/globals.css b/services/frontend/src/app/globals.css
index a31a7ddf..02f96f69 100644
--- a/services/frontend/src/app/globals.css
+++ b/services/frontend/src/app/globals.css
@@ -644,6 +644,13 @@
.animate-fade-up { animation: fade-up 0.18s cubic-bezier(0.16, 1, 0.3, 1); }
.animate-toast-in { animation: toast-in 0.2s cubic-bezier(0.16, 1, 0.3, 1); }
+/* Staggered list-item reveal — pair with inline `animationDelay` from
+ staggerDelay(i) in lib/utils.ts. Fades + slides up, forwards fill so
+ items stay put after animating. */
+.animate-stagger {
+ animation: fade-up 0.24s cubic-bezier(0.16, 1, 0.3, 1) both;
+}
+
/* Sweeping glow line across a hero/placeholder panel. */
.scan-line {
position: absolute;
@@ -690,6 +697,7 @@
.animate-eq,
.animate-fade-up,
.animate-toast-in,
+ .animate-stagger,
.scan-line,
.ambient-blob,
.ambient-mote,
diff --git a/services/frontend/src/components/LiveModerationFeed.tsx b/services/frontend/src/components/LiveModerationFeed.tsx
index e49f005e..206578a9 100644
--- a/services/frontend/src/components/LiveModerationFeed.tsx
+++ b/services/frontend/src/components/LiveModerationFeed.tsx
@@ -5,6 +5,7 @@ import { useEffect, useRef } from "react";
import { Badge } from "@/components/primitives";
import { formatRelativeTime } from "@/lib/format";
import type { ModerationAction } from "@/lib/types";
+import { staggerDelay } from "@/lib/utils";
const ACTION_LABEL: Record = {
delete_message: "Deleted",
@@ -106,12 +107,13 @@ export function LiveModerationFeed({
AWAITING MODERATION DISPATCH STREAM...
) : (
- actions.map((a) => {
+ actions.map((a, i) => {
const tone = severityTone(a.severity);
return (
{actionIcon(a.action_type)}
diff --git a/services/frontend/src/components/primitives/toast.tsx b/services/frontend/src/components/primitives/toast.tsx
index c7ed1403..d1598b0a 100644
--- a/services/frontend/src/components/primitives/toast.tsx
+++ b/services/frontend/src/components/primitives/toast.tsx
@@ -76,8 +76,10 @@ export function Toaster({ position = "bottom-right" }: { position?: string }) {
key={t.id}
className={cn(
"glass animate-toast-in pointer-events-auto flex items-start gap-3 p-3.5 transition-colors",
- t.tone === "signal" && "border-signal/30",
- t.tone === "vermilion" && "border-vermilion/30",
+ t.tone === "signal" && "border-signal/40",
+ t.tone === "vermilion" && "border-vermilion/40",
+ t.tone === "signal" &&
+ "shadow-[0_0_0_1px_var(--color-signal)/20]",
)}
>