<section
               aria-labelledby="routine-section-title"
diff --git a/ui/src/pages/Routines.test.tsx b/ui/src/pages/Routines.test.tsx
index 993f0c67..f3f795a0 100644
--- a/ui/src/pages/Routines.test.tsx
+++ b/ui/src/pages/Routines.test.tsx
@@ -1,6 +1,7 @@
 // @vitest-environment jsdom
 
-import { act, type AnchorHTMLAttributes, type ReactNode } from "react";
+import type { AnchorHTMLAttributes, ReactNode } from "react";
+import { flushSync } from "react-dom";
 import { createRoot } from "react-dom/client";
 import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
 import type { Issue, RoutineListItem } from "@paperclipai/shared";
@@ -9,6 +10,14 @@ import { Routines, buildRoutineGroups, sortRoutines } from "./Routines";
 
 let currentSearch = "";
 
+async function act(callback: () => void | Promise<void>) {
+  let result: void | Promise<void> = undefined;
+  flushSync(() => {
+    result = callback();
+  });
+  await result;
+}
+
 const navigateMock = vi.fn();
 const routinesListMock = vi.fn<(companyId: string) => Promise<RoutineListItem[]>>();
 const issuesListMock = vi.fn<(companyId: string, filters?: Record<string, unknown>) => Promise<Issue[]>>();
diff --git a/ui/src/pages/Routines.tsx b/ui/src/pages/Routines.tsx
index 0139bf45..35cf0321 100644
--- a/ui/src/pages/Routines.tsx
+++ b/ui/src/pages/Routines.tsx
@@ -885,51 +885,59 @@ export function Routines() {
               />
             </div>
           ) : (
-            <div className="rounded-lg border border-border">
-              {routineGroups.map((group) => (
-                <Collapsible
-                  key={group.key}
-                  open={!routineViewState.collapsedGroups.includes(group.key)}
-                  onOpenChange={(open) => {
-                    updateRoutineView({
-                      collapsedGroups: open
-                        ? routineViewState.collapsedGroups.filter((item) => item !== group.key)
-                        : [...routineViewState.collapsedGroups, group.key],
-                    });
-                  }}
-                >
-                  {group.label ? (
-                    <div className="flex items-center gap-2 border-b border-border px-3 py-2">
-                      <CollapsibleTrigger className="flex items-center gap-1.5">
-                        <ChevronRight className="h-3.5 w-3.5 shrink-0 text-muted-foreground transition-transform [[data-state=open]>&]:rotate-90" />
-                        <span className="text-sm font-semibold uppercase tracking-wide">
-                          {group.label}
+            <div className="flex flex-col gap-3">
+              {routineGroups.map((group) => {
+                const isOpen = !routineViewState.collapsedGroups.includes(group.key);
+                return (
+                  <Collapsible
+                    key={group.key}
+                    open={isOpen}
+                    onOpenChange={(open) => {
+                      updateRoutineView({
+                        collapsedGroups: open
+                          ? routineViewState.collapsedGroups.filter((item) => item !== group.key)
+                          : [...routineViewState.collapsedGroups, group.key],
+                      });
+                    }}
+                  >
+                    {group.label ? (
+                      <div
+                        className={`flex items-center gap-2 rounded-lg border border-border px-3 py-2${
+                          isOpen ? " mb-1" : ""
+                        }`}
+                      >
+                        <CollapsibleTrigger className="flex items-center gap-1.5">
+                          <ChevronRight className="h-3.5 w-3.5 shrink-0 text-muted-foreground transition-transform [[data-state=open]>&]:rotate-90" />
+                          <span className="text-sm font-semibold uppercase tracking-wide">
+                            {group.label}
+                          </span>
+                        </CollapsibleTrigger>
+                        <span className="text-xs text-muted-foreground">
+                          {group.items.length}
                         </span>
-                      </CollapsibleTrigger>
-                      <span className="text-xs text-muted-foreground">
-                        {group.items.length}
-                      </span>
-                    </div>
-                  ) : null}
-                  <CollapsibleContent>
-                    {group.items.map((routine) => (
-                      <RoutineListRow
-                        key={routine.id}
-                        routine={routine}
-                        projectById={projectById}
-                        agentById={agentById}
-                        runningRoutineId={runningRoutineId}
-                        statusMutationRoutineId={statusMutationRoutineId}
-                        href={`/routines/${routine.id}`}
-                        runNowButton
-                        onRunNow={handleRunNow}
-                        onToggleEnabled={handleToggleEnabled}
-                        onToggleArchived={handleToggleArchived}
-                      />
-                    ))}
-                  </CollapsibleContent>
-                </Collapsible>
-              ))}
+                      </div>
+                    ) : null}
+                    <CollapsibleContent>
+                      {group.items.map((routine) => (
+                        <RoutineListRow
+                          key={routine.id}
+                          routine={routine}
+                          projectById={projectById}
+                          agentById={agentById}
+                          runningRoutineId={runningRoutineId}
+                          statusMutationRoutineId={statusMutationRoutineId}
+                          href={`/routines/${routine.id}`}
+                          runNowButton
+                          divider={false}
+                          onRunNow={handleRunNow}
+                          onToggleEnabled={handleToggleEnabled}
+                          onToggleArchived={handleToggleArchived}
+                        />
+                      ))}
+                    </CollapsibleContent>
+                  </Collapsible>
+                );
+              })}
             </div>
           )}
         </div>
diff --git a/ui/storybook/stories/routine-detail-c.stories.tsx b/ui/storybook/stories/routine-detail-c.stories.tsx
index 82d61ae1..4b797e0d 100644
--- a/ui/storybook/stories/routine-detail-c.stories.tsx
+++ b/ui/storybook/stories/routine-detail-c.stories.tsx
@@ -83,7 +83,7 @@ const routine: RoutineDetailType = {
   concurrencyPolicy: "coalesce_if_active",
   catchUpPolicy: "skip_missed",
   variables,
-  env: { DATABASE_URL: { kind: "secret", secretId: "secret-prod-db", version: "latest" } } as never,
+  env: { DATABASE_URL: { type: "secret_ref", secretId: "secret-prod-db", version: "latest" } } as never,
   latestRevisionId: "rev-17",
   latestRevisionNumber: 17,
   createdByAgentId: null,
@@ -104,9 +104,41 @@ const routine: RoutineDetailType = {
 };
 
 const routineRuns = [
-  { id: "run-1", source: "manual", status: "succeeded", triggeredAt: new Date("2026-06-09T11:48:00Z"), trigger: { label: "manual", kind: "manual" }, linkedIssue: { id: "issue-1", identifier: "PAP-99221" } },
-  { id: "run-2", source: "schedule", status: "failed", triggeredAt: new Date("2026-06-08T14:00:00Z"), trigger: { label: "schedule", kind: "schedule" }, linkedIssue: { id: "issue-2", identifier: "PAP-99220" } },
-  { id: "run-3", source: "schedule", status: "succeeded", triggeredAt: new Date("2026-06-07T14:00:00Z"), trigger: { label: "schedule", kind: "schedule" }, linkedIssue: { id: "issue-3", identifier: "PAP-99219" } },
+  { id: "run-1", source: "manual", status: "succeeded", triggeredAt: new Date("2026-06-09T11:48:00Z"), failureReason: null, triggerPayload: { customer_name: "Acme", deadline: "Fri" }, trigger: { label: "manual", kind: "manual" }, linkedIssue: { id: "issue-1", identifier: "PAP-99221", title: "Weekly digest for Acme" } },
+  { id: "run-2", source: "schedule", status: "failed", triggeredAt: new Date("2026-06-08T14:00:00Z"), failureReason: "Cron timed out after 600s", triggerPayload: { customer_name: "Acme" }, trigger: { label: "schedule", kind: "schedule" }, linkedIssue: { id: "issue-2", identifier: "PAP-99220", title: "Weekly digest for Acme" } },
+  { id: "run-3", source: "schedule", status: "succeeded", triggeredAt: new Date("2026-06-07T14:00:00Z"), failureReason: null, triggerPayload: { customer_name: "Globex" }, trigger: { label: "schedule", kind: "schedule" }, linkedIssue: { id: "issue-3", identifier: "PAP-99219", title: "Weekly digest for Globex" } },
+] as never;
+
+function stubSecret(id: string, name: string, latestVersion: number, referenceCount: number) {
+  return {
+    id,
+    companyId: COMPANY_ID,
+    key: name.toLowerCase(),
+    name,
+    provider: "paperclip",
+    status: "active",
+    managedMode: "managed",
+    externalRef: null,
+    providerConfigId: null,
+    providerMetadata: null,
+    latestVersion,
+    description: null,
+    lastResolvedAt: now,
+    lastRotatedAt: null,
+    deletedAt: null,
+    createdByAgentId: null,
+    createdByUserId: null,
+    referenceCount,
+    createdAt: now,
+    updatedAt: now,
+  };
+}
+
+const availableSecrets = [
+  stubSecret("secret-prod-db", "prod-db", 3, 5),
+  stubSecret("secret-gh-token", "gh-token", 2, 4),
+  stubSecret("secret-openai", "openai-key", 1, 2),
+  stubSecret("secret-stripe", "stripe-key", 1, 1),
 ] as never;
 
 const activity = [
@@ -172,7 +204,7 @@ function makeContext(dirty: boolean, navigate: (s: RoutineSectionKey) => void):
     secretMessage: null,
     setSecretMessage: () => {},
     copySecretValue: () => {},
-    availableSecrets: [],
+    availableSecrets,
     createSecret: stubMutation(),
     agents: storybookAgents,
     projects: storybookProjects,
@@ -238,8 +270,8 @@ function RoutineCShell({ initialSection = "overview", dirty = false }: { initial
 
   return (
     <RoutineDetailContext.Provider value={ctx}>
-      <div className="flex h-[900px] flex-col bg-background text-foreground">
-        <header className="sticky top-0 z-20 flex h-14 shrink-0 items-center gap-3 border-b border-border bg-background px-6">
+      <div className="flex h-[900px] flex-col overflow-y-auto bg-background text-foreground">
+        <header className="flex h-14 shrink-0 items-center gap-3 border-b border-border bg-background px-6">
           <div className="flex min-w-0 flex-1 items-center gap-3">
             <span className="truncate text-base font-semibold">{routine.title}</span>
             <Badge variant="outline" className="hidden shrink-0 gap-1.5 text-xs text-muted-foreground sm:inline-flex">
@@ -265,7 +297,7 @@ function RoutineCShell({ initialSection = "overview", dirty = false }: { initial
             hasLiveRun={false}
             onNavigate={setSection}
           />
-          <main className="min-w-0 flex-1 overflow-y-auto px-4 py-6 md:px-8">
+          <main className="min-w-0 flex-1 px-4 pb-6 pt-10 md:px-8">
             <section className={isEditable ? "mx-auto w-full max-w-3xl" : "w-full"}>
               <h2 className="mb-4 text-lg font-semibold">{SECTION_TITLES[section]}</h2>
               <SectionBody section={section} />
diff --git a/ui/storybook/stories/routines-list-groups.stories.tsx b/ui/storybook/stories/routines-list-groups.stories.tsx
new file mode 100644
index 00000000..cf4da482
--- /dev/null
+++ b/ui/storybook/stories/routines-list-groups.stories.tsx
@@ -0,0 +1,102 @@
+import type { Meta, StoryObj } from "@storybook/react-vite";
+import { useState } from "react";
+import { ChevronRight } from "lucide-react";
+import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
+import {
+  RoutineListRow,
+  type RoutineListAgentSummary,
+  type RoutineListProjectSummary,
+  type RoutineListRowItem,
+} from "@/components/RoutineList";
+
+const projectById = new Map<string, RoutineListProjectSummary>([
+  ["p1", { name: "Board UI", color: "#6366f1" }],
+  ["p2", { name: "Growth", color: "#10b981" }],
+]);
+const agentById = new Map<string, RoutineListAgentSummary>([
+  ["a1", { name: "CodexCoder", icon: null }],
+  ["a2", { name: "Digest Bot", icon: null }],
+]);
+
+type Group = { key: string; label: string | null; items: RoutineListRowItem[] };
+
+const GROUPS: Group[] = [
+  {
+    key: "p1",
+    label: "Board UI",
+    items: [
+      { id: "r1", title: "Weekly digest", status: "active", projectId: "p1", assigneeAgentId: "a2", lastRun: { triggeredAt: "2026-06-09T08:00:00Z", status: "succeeded" } },
+      { id: "r2", title: "Triage stale issues", status: "active", projectId: "p1", assigneeAgentId: "a1", lastRun: { triggeredAt: "2026-06-08T08:00:00Z", status: "succeeded" } },
+      { id: "r3", title: "Nightly changelog draft", status: "paused", projectId: "p1", assigneeAgentId: "a1", lastRun: null },
+    ],
+  },
+  {
+    key: "p2",
+    label: "Growth",
+    items: [
+      { id: "r4", title: "Lead enrichment sweep", status: "active", projectId: "p2", assigneeAgentId: "a1", lastRun: { triggeredAt: "2026-06-09T06:00:00Z", status: "running" } },
+      { id: "r5", title: "Outbound follow-ups", status: "active", projectId: "p2", assigneeAgentId: "a2", lastRun: { triggeredAt: "2026-06-07T06:00:00Z", status: "failed" } },
+    ],
+  },
+];
+
+function GroupedList() {
+  const [collapsed, setCollapsed] = useState<string[]>([]);
+  return (
+    <div className="mx-auto max-w-4xl p-6">
+      <div className="flex flex-col gap-3">
+        {GROUPS.map((group) => {
+          const isOpen = !collapsed.includes(group.key);
+          return (
+            <Collapsible
+              key={group.key}
+              open={isOpen}
+              onOpenChange={(open) =>
+                setCollapsed((prev) => (open ? prev.filter((k) => k !== group.key) : [...prev, group.key]))
+              }
+            >
+              {group.label ? (
+                <div className={`flex items-center gap-2 rounded-lg border border-border px-3 py-2${isOpen ? " mb-1" : ""}`}>
+                  <CollapsibleTrigger className="flex items-center gap-1.5">
+                    <ChevronRight className="h-3.5 w-3.5 shrink-0 text-muted-foreground transition-transform [[data-state=open]>&]:rotate-90" />
+                    <span className="text-sm font-semibold uppercase tracking-wide">{group.label}</span>
+                  </CollapsibleTrigger>
+                  <span className="text-xs text-muted-foreground">{group.items.length}</span>
+                </div>
+              ) : null}
+              <CollapsibleContent>
+                {group.items.map((routine) => (
+                  <RoutineListRow
+                    key={routine.id}
+                    routine={routine}
+                    projectById={projectById}
+                    agentById={agentById}
+                    runningRoutineId={null}
+                    statusMutationRoutineId={null}
+                    href={`#${routine.id}`}
+                    runNowButton
+                    divider={false}
+                    onRunNow={() => {}}
+                    onToggleEnabled={() => {}}
+                    onToggleArchived={() => {}}
+                  />
+                ))}
+              </CollapsibleContent>
+            </Collapsible>
+          );
+        })}
+      </div>
+    </div>
+  );
+}
+
+const meta: Meta<typeof GroupedList> = {
+  title: "Product/Routines · List (grouped cards)",
+  component: GroupedList,
+  parameters: { layout: "fullscreen" },
+};
+export default meta;
+
+type Story = StoryObj<typeof GroupedList>;
+
+export const GroupedCards: Story = {};