diff --git a/ui/src/pages/AppsTab/AppDeploymentPage/Checkpoints.tsx b/ui/src/pages/AppsTab/AppDeploymentPage/Checkpoints.tsx
deleted file mode 100644
index 281bb0ad..00000000
--- a/ui/src/pages/AppsTab/AppDeploymentPage/Checkpoints.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import { useState } from 'react';
-import { Button, Badge } from '@cameleer/design-system';
-import type { Deployment, AppVersion } from '../../../api/queries/admin/apps';
-import { timeAgo } from '../../../utils/format-utils';
-import styles from './AppDeploymentPage.module.css';
-
-interface CheckpointsProps {
- deployments: Deployment[];
- versions: AppVersion[];
- currentDeploymentId: string | null;
- onRestore: (deploymentId: string) => void;
-}
-
-export function Checkpoints({ deployments, versions, currentDeploymentId, onRestore }: CheckpointsProps) {
- const [open, setOpen] = useState(false);
- const versionMap = new Map(versions.map((v) => [v.id, v]));
-
- // Any deployment that captured a snapshot is restorable — that covers RUNNING,
- // DEGRADED, and STOPPED (blue/green swap previous, user-stopped). Exclude the
- // currently-running one and anything without a snapshot (FAILED, STARTING).
- const checkpoints = deployments
- .filter((d) => d.deployedConfigSnapshot && d.id !== currentDeploymentId)
- .sort((a, b) => (b.deployedAt ?? '').localeCompare(a.deployedAt ?? ''));
-
- return (
-
-
- {open && (
-
- {checkpoints.length === 0 && (
-
No past deployments yet.
- )}
- {checkpoints.map((d) => {
- const v = versionMap.get(d.appVersionId);
- const jarAvailable = !!v;
- return (
-
-
-
- {d.deployedAt ? timeAgo(d.deployedAt) : '—'}
-
- {!jarAvailable && (
- archived, JAR unavailable
- )}
-
-
- );
- })}
-
- )}
-
- );
-}
diff --git a/ui/src/pages/AppsTab/AppDeploymentPage/index.tsx b/ui/src/pages/AppsTab/AppDeploymentPage/index.tsx
index 6c5d2054..702ce4ee 100644
--- a/ui/src/pages/AppsTab/AppDeploymentPage/index.tsx
+++ b/ui/src/pages/AppsTab/AppDeploymentPage/index.tsx
@@ -20,7 +20,8 @@ import type { Deployment } from '../../../api/queries/admin/apps';
import { useApplicationConfig, useUpdateApplicationConfig } from '../../../api/queries/commands';
import { PageLoader } from '../../../components/PageLoader';
import { IdentitySection } from './IdentitySection';
-import { Checkpoints } from './Checkpoints';
+import { CheckpointsTable } from './CheckpointsTable';
+import { CheckpointDetailDrawer } from './CheckpointDetailDrawer';
import { MonitoringTab } from './ConfigTabs/MonitoringTab';
import { ResourcesTab } from './ConfigTabs/ResourcesTab';
import { VariablesTab } from './ConfigTabs/VariablesTab';
@@ -90,6 +91,7 @@ export default function AppDeploymentPage() {
const [tab, setTab] = useState('monitoring');
const [deleteConfirm, setDeleteConfirm] = useState(false);
const [stopTarget, setStopTarget] = useState(null);
+ const [selectedCheckpointId, setSelectedCheckpointId] = useState(null);
const lastDerivedRef = useRef('');
// Initialize name from app when it loads
@@ -348,6 +350,15 @@ export default function AppDeploymentPage() {
return true; // redeploy always enabled
})();
+ // Checkpoint drawer derivations
+ const jarRetentionCount = env?.jarRetentionCount ?? null;
+ const selectedDep = selectedCheckpointId
+ ? deployments.find((d) => d.id === selectedCheckpointId) ?? null
+ : null;
+ const selectedDepVersion = selectedDep
+ ? versions.find((v) => v.id === selectedDep.appVersionId)
+ : undefined;
+
// ── Loading guard ──────────────────────────────────────────────────
if (envLoading || appsLoading) return ;
if (!env) return Select an environment first.
;
@@ -435,12 +446,30 @@ export default function AppDeploymentPage() {
deploying={deploymentInProgress}
>
{app && (
-
+ <>
+
+ {selectedDep && (
+ setSelectedCheckpointId(null)}
+ deployment={selectedDep}
+ version={selectedDepVersion}
+ appSlug={app.slug}
+ envSlug={selectedEnv ?? ''}
+ currentForm={form}
+ onRestore={(deploymentId) => {
+ handleRestore(deploymentId);
+ setSelectedCheckpointId(null);
+ }}
+ />
+ )}
+ >
)}