diff --git a/src/components/DevboxDetailPage.tsx b/src/components/DevboxDetailPage.tsx
index 78240186..5b04a7c5 100644
--- a/src/components/DevboxDetailPage.tsx
+++ b/src/components/DevboxDetailPage.tsx
@@ -6,6 +6,7 @@ import { StatusBadge } from "./StatusBadge.js";
import { MetadataDisplay } from "./MetadataDisplay.js";
import { Breadcrumb } from "./Breadcrumb.js";
import { DevboxActionsMenu } from "./DevboxActionsMenu.js";
+import { StateHistory } from "./StateHistory.js";
import { getDevboxUrl } from "../utils/url.js";
import { colors } from "../utils/theme.js";
import { useViewportHeight } from "../hooks/useViewportHeight.js";
@@ -840,6 +841,9 @@ export const DevboxDetailPage = ({
)}
+ {/* State History */}
+
+
{/* Operations - inline display */}
diff --git a/src/components/StateHistory.tsx b/src/components/StateHistory.tsx
new file mode 100644
index 00000000..54498e35
--- /dev/null
+++ b/src/components/StateHistory.tsx
@@ -0,0 +1,131 @@
+import React from "react";
+import { Box, Text } from "ink";
+import figures from "figures";
+import { colors } from "../utils/theme.js";
+
+interface StateHistoryProps {
+ stateTransitions?: any;
+}
+
+// Format time ago in a succinct way
+const formatTimeAgo = (timestamp: number): string => {
+ const seconds = Math.floor((Date.now() - timestamp) / 1000);
+
+ if (seconds < 60) return `${seconds}s ago`;
+
+ const minutes = Math.floor(seconds / 60);
+ if (minutes < 60) return `${minutes}m ago`;
+
+ const hours = Math.floor(minutes / 60);
+ if (hours < 24) return `${hours}h ago`;
+
+ const days = Math.floor(hours / 24);
+ if (days < 30) return `${days}d ago`;
+
+ const months = Math.floor(days / 30);
+ if (months < 12) return `${months}mo ago`;
+
+ const years = Math.floor(months / 12);
+ return `${years}y ago`;
+};
+
+// Format duration in a succinct way
+const formatDuration = (milliseconds: number): string => {
+ const seconds = Math.floor(milliseconds / 1000);
+ if (seconds < 60) return `${seconds}s`;
+
+ const minutes = Math.floor(seconds / 60);
+ if (minutes < 60) return `${minutes}m`;
+
+ const hours = Math.floor(minutes / 60);
+ if (hours < 24) return `${hours}h ${minutes % 60}m`;
+
+ const days = Math.floor(hours / 24);
+ return `${days}d ${hours % 24}h`;
+};
+
+// Capitalize first letter of a string
+const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);
+
+export const StateHistory = ({ stateTransitions }: StateHistoryProps) => {
+ if (
+ !stateTransitions ||
+ !Array.isArray(stateTransitions) ||
+ stateTransitions.length === 0
+ ) {
+ return null;
+ }
+
+ // Get last 3 transitions (most recent first)
+ const lastThree = (
+ stateTransitions as Array<{
+ status: string;
+ transition_time_ms?: number;
+ }>
+ )
+ .slice(-3)
+ .reverse()
+ .map((transition, idx, arr) => {
+ const transitionTime = transition.transition_time_ms;
+ // Calculate duration: time until next transition, or until now if it's the current state
+ let duration = 0;
+ if (transitionTime) {
+ if (idx === 0) {
+ // Most recent state - duration is from transition time to now
+ duration = Date.now() - transitionTime;
+ } else {
+ // Previous state - duration is from this transition to the next one
+ const nextTransition = arr[idx - 1];
+ const nextTransitionTime = nextTransition.transition_time_ms;
+ if (nextTransitionTime) {
+ duration = nextTransitionTime - transitionTime;
+ }
+ }
+ }
+ return {
+ status: transition.status,
+ transitionTime,
+ duration,
+ };
+ })
+ .filter((state) => state.transitionTime); // Only show states with valid timestamps
+
+ if (lastThree.length === 0) {
+ return null;
+ }
+
+ return (
+
+
+ {figures.circleFilled} State History
+
+
+ {lastThree.map((state, idx) => (
+
+
+ {capitalize(state.status)}
+ {state.transitionTime && (
+ <>
+ {" "}
+ at {new Date(state.transitionTime).toLocaleString()}{" "}
+
+ ({formatTimeAgo(state.transitionTime)})
+
+ {state.duration > 0 && (
+ <>
+ {" "}
+ • Duration:{" "}
+
+ {formatDuration(state.duration)}
+
+ >
+ )}
+ >
+ )}
+
+
+ ))}
+
+
+ );
+};
diff --git a/src/utils/theme.ts b/src/utils/theme.ts
index 1d57bc02..3f091c09 100644
--- a/src/utils/theme.ts
+++ b/src/utils/theme.ts
@@ -43,7 +43,7 @@ const darkColors: ColorPalette = {
// UI colors
text: "#FFFFFF", // White
- textDim: "#9CA3AF", // Gray
+ textDim: "#B8C2D0", // Brighter gray for better visibility in dark mode
border: "#6B7280", // Medium gray
background: "#000000", // Black