Repository: All-Hands-AI/OpenHands
Pull request #7435: Fix conversation list: remove GitHub link and show created_at date
Author: login: rbren
Created: 2025-03-23T01:37:11Z | Merged: 2025-03-24T02:08:03Z

This PR implements two fixes for the conversation list:

1. Removes the link to GitHub when a repo is attached (changes the button to a plain text span)
2. Shows the `created_at` date rather than `updated_at` in the conversation card

The changes are backward compatible, with `createdAt` being an optional prop that falls back to `lastUpdatedAt` if not provided.

---

To run this PR locally, use the following command:
```
docker run -it --rm   -p 3000:3000   -v /var/run/docker.sock:/var/run/docker.sock   --add-host host.docker.internal:host-gateway   -e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:e0571b3-nikolaik   --name openhands-app-e0571b3   docker.all-hands.dev/all-hands-ai/openhands:e0571b3
```


Diff:
diff --git a/README.md b/README.md
index 904cdc738c2b..282f0712e59c 100644
--- a/README.md
+++ b/README.md
@@ -57,7 +57,7 @@ docker run -it --rm --pull=always \
 ```
 
 > [!WARNING]
-> On a public network? See our [Hardened Docker Installation](https://docs.all-hands.dev/modules/usage/runtimes/docker#hardened-docker-installation) guide 
+> On a public network? See our [Hardened Docker Installation](https://docs.all-hands.dev/modules/usage/runtimes/docker#hardened-docker-installation) guide
 > to secure your deployment by restricting network binding and implementing additional security measures.
 
 You'll find OpenHands running at [http://localhost:3000](http://localhost:3000)!
diff --git a/docs/modules/usage/runtimes-index.md b/docs/modules/usage/runtimes-index.md
index c29dfebe271b..e9f1dc24ed36 100644
--- a/docs/modules/usage/runtimes-index.md
+++ b/docs/modules/usage/runtimes-index.md
@@ -21,4 +21,4 @@ OpenHands supports several different runtime environments:
 - [OpenHands Remote Runtime](./runtimes/remote.md) - Cloud-based runtime for parallel execution (beta)
 - [Modal Runtime](./runtimes/modal.md) - Runtime provided by our partners at Modal
 - [Daytona Runtime](./runtimes/daytona.md) - Runtime provided by Daytona
-- [Local Runtime](./runtimes/local.md) - Direct execution on your local machine without Docker
\ No newline at end of file
+- [Local Runtime](./runtimes/local.md) - Direct execution on your local machine without Docker
diff --git a/docs/modules/usage/runtimes/daytona.md b/docs/modules/usage/runtimes/daytona.md
index 7424241f0581..107f957c5ad2 100644
--- a/docs/modules/usage/runtimes/daytona.md
+++ b/docs/modules/usage/runtimes/daytona.md
@@ -29,4 +29,4 @@ bash -i <(curl -sL https://get.daytona.io/openhands)
 
 Once executed, OpenHands should be running locally and ready for use.
 
-For more details and manual initialization, view the entire [README.md](https://github.com/All-Hands-AI/OpenHands/blob/main/openhands/runtime/impl/daytona/README.md)
\ No newline at end of file
+For more details and manual initialization, view the entire [README.md](https://github.com/All-Hands-AI/OpenHands/blob/main/openhands/runtime/impl/daytona/README.md)
diff --git a/docs/modules/usage/runtimes/local.md b/docs/modules/usage/runtimes/local.md
index 2fb1ec4f7872..46e9a631e941 100644
--- a/docs/modules/usage/runtimes/local.md
+++ b/docs/modules/usage/runtimes/local.md
@@ -59,4 +59,4 @@ The Local Runtime is particularly useful for:
 - CI/CD pipelines where Docker is not available.
 - Testing and development of OpenHands itself.
 - Environments where container usage is restricted.
-- Scenarios where direct file system access is required.
\ No newline at end of file
+- Scenarios where direct file system access is required.
diff --git a/docs/modules/usage/runtimes/modal.md b/docs/modules/usage/runtimes/modal.md
index 590fbc8c6432..bcfbd0085cf7 100644
--- a/docs/modules/usage/runtimes/modal.md
+++ b/docs/modules/usage/runtimes/modal.md
@@ -10,4 +10,4 @@ docker run # ...
     -e RUNTIME=modal \
     -e MODAL_API_TOKEN_ID="your-id" \
     -e MODAL_API_TOKEN_SECRET="your-secret" \
-```
\ No newline at end of file
+```
diff --git a/docs/modules/usage/runtimes/remote.md b/docs/modules/usage/runtimes/remote.md
index c6688136ba5f..2df02d6356e7 100644
--- a/docs/modules/usage/runtimes/remote.md
+++ b/docs/modules/usage/runtimes/remote.md
@@ -3,4 +3,4 @@
 OpenHands Remote Runtime is currently in beta (read [here](https://runtime.all-hands.dev/) for more details), it allows you to launch runtimes in parallel in the cloud.
 Fill out [this form](https://docs.google.com/forms/d/e/1FAIpQLSckVz_JFwg2_mOxNZjCtr7aoBFI2Mwdan3f75J_TrdMS1JV2g/viewform) to apply if you want to try this out!
 
-NOTE: This runtime is specifically designed for agent evaluation purposes only through [OpenHands evaluation harness](https://github.com/All-Hands-AI/OpenHands/tree/main/evaluation). It should not be used to launch production OpenHands applications.
\ No newline at end of file
+NOTE: This runtime is specifically designed for agent evaluation purposes only through [OpenHands evaluation harness](https://github.com/All-Hands-AI/OpenHands/tree/main/evaluation). It should not be used to launch production OpenHands applications.
diff --git a/frontend/__tests__/utils/format-time-delta.test.ts b/frontend/__tests__/utils/format-time-delta.test.ts
index 283001bf2042..496643a11870 100644
--- a/frontend/__tests__/utils/format-time-delta.test.ts
+++ b/frontend/__tests__/utils/format-time-delta.test.ts
@@ -9,67 +9,67 @@ describe("formatTimeDelta", () => {
 
   it("formats the yearly time correctly", () => {
     const oneYearAgo = new Date("2023-01-01T00:00:00Z");
-    expect(formatTimeDelta(oneYearAgo)).toBe("1 year");
+    expect(formatTimeDelta(oneYearAgo)).toBe("1y");
 
     const twoYearsAgo = new Date("2022-01-01T00:00:00Z");
-    expect(formatTimeDelta(twoYearsAgo)).toBe("2 years");
+    expect(formatTimeDelta(twoYearsAgo)).toBe("2y");
 
     const threeYearsAgo = new Date("2021-01-01T00:00:00Z");
-    expect(formatTimeDelta(threeYearsAgo)).toBe("3 years");
+    expect(formatTimeDelta(threeYearsAgo)).toBe("3y");
   });
 
   it("formats the monthly time correctly", () => {
     const oneMonthAgo = new Date("2023-12-01T00:00:00Z");
-    expect(formatTimeDelta(oneMonthAgo)).toBe("1 month");
+    expect(formatTimeDelta(oneMonthAgo)).toBe("1mo");
 
     const twoMonthsAgo = new Date("2023-11-01T00:00:00Z");
-    expect(formatTimeDelta(twoMonthsAgo)).toBe("2 months");
+    expect(formatTimeDelta(twoMonthsAgo)).toBe("2mo");
 
     const threeMonthsAgo = new Date("2023-10-01T00:00:00Z");
-    expect(formatTimeDelta(threeMonthsAgo)).toBe("3 months");
+    expect(formatTimeDelta(threeMonthsAgo)).toBe("3mo");
   });
 
   it("formats the daily time correctly", () => {
     const oneDayAgo = new Date("2023-12-31T00:00:00Z");
-    expect(formatTimeDelta(oneDayAgo)).toBe("1 day");
+    expect(formatTimeDelta(oneDayAgo)).toBe("1d");
 
     const twoDaysAgo = new Date("2023-12-30T00:00:00Z");
-    expect(formatTimeDelta(twoDaysAgo)).toBe("2 days");
+    expect(formatTimeDelta(twoDaysAgo)).toBe("2d");
 
     const threeDaysAgo = new Date("2023-12-29T00:00:00Z");
-    expect(formatTimeDelta(threeDaysAgo)).toBe("3 days");
+    expect(formatTimeDelta(threeDaysAgo)).toBe("3d");
   });
 
   it("formats the hourly time correctly", () => {
     const oneHourAgo = new Date("2023-12-31T23:00:00Z");
-    expect(formatTimeDelta(oneHourAgo)).toBe("1 hour");
+    expect(formatTimeDelta(oneHourAgo)).toBe("1h");
 
     const twoHoursAgo = new Date("2023-12-31T22:00:00Z");
-    expect(formatTimeDelta(twoHoursAgo)).toBe("2 hours");
+    expect(formatTimeDelta(twoHoursAgo)).toBe("2h");
 
     const threeHoursAgo = new Date("2023-12-31T21:00:00Z");
-    expect(formatTimeDelta(threeHoursAgo)).toBe("3 hours");
+    expect(formatTimeDelta(threeHoursAgo)).toBe("3h");
   });
 
   it("formats the minute time correctly", () => {
     const oneMinuteAgo = new Date("2023-12-31T23:59:00Z");
-    expect(formatTimeDelta(oneMinuteAgo)).toBe("1 minute");
+    expect(formatTimeDelta(oneMinuteAgo)).toBe("1m");
 
     const twoMinutesAgo = new Date("2023-12-31T23:58:00Z");
-    expect(formatTimeDelta(twoMinutesAgo)).toBe("2 minutes");
+    expect(formatTimeDelta(twoMinutesAgo)).toBe("2m");
 
     const threeMinutesAgo = new Date("2023-12-31T23:57:00Z");
-    expect(formatTimeDelta(threeMinutesAgo)).toBe("3 minutes");
+    expect(formatTimeDelta(threeMinutesAgo)).toBe("3m");
   });
 
   it("formats the second time correctly", () => {
     const oneSecondAgo = new Date("2023-12-31T23:59:59Z");
-    expect(formatTimeDelta(oneSecondAgo)).toBe("1 second");
+    expect(formatTimeDelta(oneSecondAgo)).toBe("1s");
 
     const twoSecondsAgo = new Date("2023-12-31T23:59:58Z");
-    expect(formatTimeDelta(twoSecondsAgo)).toBe("2 seconds");
+    expect(formatTimeDelta(twoSecondsAgo)).toBe("2s");
 
     const threeSecondsAgo = new Date("2023-12-31T23:59:57Z");
-    expect(formatTimeDelta(threeSecondsAgo)).toBe("3 seconds");
+    expect(formatTimeDelta(threeSecondsAgo)).toBe("3s");
   });
 });
diff --git a/frontend/src/components/features/conversation-panel/conversation-card.tsx b/frontend/src/components/features/conversation-panel/conversation-card.tsx
index 9b89d174cd2a..00fd0d765f56 100644
--- a/frontend/src/components/features/conversation-panel/conversation-card.tsx
+++ b/frontend/src/components/features/conversation-panel/conversation-card.tsx
@@ -22,11 +22,14 @@ interface ConversationCardProps {
   title: string;
   selectedRepository: string | null;
   lastUpdatedAt: string; // ISO 8601
+  createdAt?: string; // ISO 8601
   status?: ProjectStatus;
   variant?: "compact" | "default";
   conversationId?: string; // Optional conversation ID for VS Code URL
 }
 
+const MAX_TIME_BETWEEN_CREATION_AND_UPDATE = 1000 * 60 * 30; // 30 minutes
+
 export function ConversationCard({
   onClick,
   onDelete,
@@ -35,7 +38,10 @@ export function ConversationCard({
   isActive,
   title,
   selectedRepository,
+  // lastUpdatedAt is kept in props for backward compatibility
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars
   lastUpdatedAt,
+  createdAt,
   status = "STOPPED",
   variant = "default",
   conversationId,
@@ -105,11 +111,10 @@ export function ConversationCard({
 
         if (data.vscode_url) {
           window.open(data.vscode_url, "_blank");
-        } else {
-          console.error("VS Code URL not available", data.error);
         }
+        // VS Code URL not available
       } catch (error) {
-        console.error("Failed to fetch VS Code URL", error);
+        // Failed to fetch VS Code URL
       }
     }
 
@@ -128,6 +133,12 @@ export function ConversationCard({
   }, [titleMode]);
 
   const hasContextMenu = !!(onDelete || onChangeTitle || showDisplayCostOption);
+  const timeBetweenUpdateAndCreation = createdAt
+    ? new Date(lastUpdatedAt).getTime() - new Date(createdAt).getTime()
+    : 0;
+  const showUpdateTime =
+    createdAt &&
+    timeBetweenUpdateAndCreation > MAX_TIME_BETWEEN_CREATION_AND_UPDATE;
 
   return (
     <>
@@ -205,7 +216,16 @@ export function ConversationCard({
             <ConversationRepoLink selectedRepository={selectedRepository} />
           )}
           <p className="text-xs text-neutral-400">
-            <time>{formatTimeDelta(new Date(lastUpdatedAt))} ago</time>
+            <span>Created </span>
+            <time>
+              {formatTimeDelta(new Date(createdAt || lastUpdatedAt))} ago
+            </time>
+            {showUpdateTime && (
+              <>
+                <span>, updated </span>
+                <time>{formatTimeDelta(new Date(lastUpdatedAt))} ago</time>
+              </>
+            )}
           </p>
         </div>
       </div>
diff --git a/frontend/src/components/features/conversation-panel/conversation-panel.tsx b/frontend/src/components/features/conversation-panel/conversation-panel.tsx
index d669748ba8fc..f35ed756ad33 100644
--- a/frontend/src/components/features/conversation-panel/conversation-panel.tsx
+++ b/frontend/src/components/features/conversation-panel/conversation-panel.tsx
@@ -108,7 +108,9 @@ export function ConversationPanel({ onClose }: ConversationPanelProps) {
               title={project.title}
               selectedRepository={project.selected_repository}
               lastUpdatedAt={project.last_updated_at}
+              createdAt={project.created_at}
               status={project.status}
+              conversationId={project.conversation_id}
             />
           )}
         </NavLink>
diff --git a/frontend/src/components/features/conversation-panel/conversation-repo-link.tsx b/frontend/src/components/features/conversation-panel/conversation-repo-link.tsx
index 5772ee41f1b9..655a67d53e90 100644
--- a/frontend/src/components/features/conversation-panel/conversation-repo-link.tsx
+++ b/frontend/src/components/features/conversation-panel/conversation-repo-link.tsx
@@ -5,23 +5,12 @@ interface ConversationRepoLinkProps {
 export function ConversationRepoLink({
   selectedRepository,
 }: ConversationRepoLinkProps) {
-  const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
-    event.preventDefault();
-    window.open(
-      `https://github.com/${selectedRepository}`,
-      "_blank",
-      "noopener,noreferrer",
-    );
-  };
-
   return (
-    <button
-      type="button"
+    <span
       data-testid="conversation-card-selected-repository"
-      onClick={handleClick}
-      className="text-xs text-neutral-400 hover:text-neutral-200"
+      className="text-xs text-neutral-400"
     >
       {selectedRepository}
-    </button>
+    </span>
   );
 }
diff --git a/frontend/src/utils/format-time-delta.ts b/frontend/src/utils/format-time-delta.ts
index b384614394a7..3bd43f14f5cb 100644
--- a/frontend/src/utils/format-time-delta.ts
+++ b/frontend/src/utils/format-time-delta.ts
@@ -1,12 +1,12 @@
 /**
- * Formats a date into a human-readable string representing the time delta between the given date and the current date.
+ * Formats a date into a compact string representing the time delta between the given date and the current date.
  * @param date The date to format
- * @returns A human-readable string representing the time delta between the given date and the current date
+ * @returns A compact string representing the time delta between the given date and the current date
  *
  * @example
  * // now is 2024-01-01T00:00:00Z
- * formatTimeDelta(new Date("2023-12-31T23:59:59Z")); // "1 second"
- * formatTimeDelta(new Date("2022-01-01T00:00:00Z")); // "2 years"
+ * formatTimeDelta(new Date("2023-12-31T23:59:59Z")); // "1s"
+ * formatTimeDelta(new Date("2022-01-01T00:00:00Z")); // "2y"
  */
 export const formatTimeDelta = (date: Date) => {
   const now = new Date();
@@ -19,11 +19,10 @@ export const formatTimeDelta = (date: Date) => {
   const months = Math.floor(days / 30);
   const years = Math.floor(months / 12);
 
-  if (seconds < 60) return seconds === 1 ? "1 second" : `${seconds} seconds`;
-  if (minutes < 60) return minutes === 1 ? "1 minute" : `${minutes} minutes`;
-  if (hours < 24) return hours === 1 ? "1 hour" : `${hours} hours`;
-  if (days < 30) return days === 1 ? "1 day" : `${days} days`;
-  if (months < 12) return months === 1 ? "1 month" : `${months} months`;
-
-  return years === 1 ? "1 year" : `${years} years`;
+  if (seconds < 60) return `${seconds}s`;
+  if (minutes < 60) return `${minutes}m`;
+  if (hours < 24) return `${hours}h`;
+  if (days < 30) return `${days}d`;
+  if (months < 12) return `${months}mo`;
+  return `${years}y`;
 };
diff --git a/openhands/runtime/impl/action_execution/action_execution_client.py b/openhands/runtime/impl/action_execution/action_execution_client.py
index 66f6efc34182..1640958026d8 100644
--- a/openhands/runtime/impl/action_execution/action_execution_client.py
+++ b/openhands/runtime/impl/action_execution/action_execution_client.py
@@ -60,7 +60,7 @@ def __init__(
         attach_to_existing: bool = False,
         headless_mode: bool = True,
         user_id: str | None = None,
-        git_provider_tokens: PROVIDER_TOKEN_TYPE | None = None
+        git_provider_tokens: PROVIDER_TOKEN_TYPE | None = None,
     ):
         self.session = HttpSession()
         self.action_semaphore = threading.Semaphore(1)  # Ensure one action at a time
diff --git a/openhands/server/session/conversation_init_data.py b/openhands/server/session/conversation_init_data.py
index 1ce27e3e9641..397d119c5061 100644
--- a/openhands/server/session/conversation_init_data.py
+++ b/openhands/server/session/conversation_init_data.py
@@ -1,4 +1,3 @@
-from types import MappingProxyType
 from pydantic import Field
 
 from openhands.integrations.provider import PROVIDER_TOKEN_TYPE
@@ -16,4 +15,4 @@ class ConversationInitData(Settings):
 
     model_config = {
         'arbitrary_types_allowed': True,
-    }
\ No newline at end of file
+    }
diff --git a/openhands/server/session/session.py b/openhands/server/session/session.py
index 346c5be2f059..f1d74799e0ed 100644
--- a/openhands/server/session/session.py
+++ b/openhands/server/session/session.py
@@ -23,7 +23,6 @@
 from openhands.events.observation.error import ErrorObservation
 from openhands.events.serialization import event_from_dict, event_to_dict
 from openhands.events.stream import EventStreamSubscriber
-from openhands.integrations.provider import PROVIDER_TOKEN_TYPE
 from openhands.llm.llm import LLM
 from openhands.server.session.agent_session import AgentSession
 from openhands.server.session.conversation_init_data import ConversationInitData


Reviews:
user: login: enyst
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-23T01:41:49Z
user: login: rbren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-23T01:46:07Z
user: login: enyst
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-23T01:47:29Z
user: login: enyst
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-23T01:48:27Z
user: login: enyst
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-23T01:49:52Z
user: login: enyst
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-23T01:53:06Z
user: login: rbren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-23T01:59:41Z
user: login: enyst
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-23T02:06:15Z
user: login: enyst
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-23T02:11:46Z
user: login: enyst
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-23T02:22:38Z
user: login: enyst
body: OK so what I was thinking here is no good, because it would actually flip all the time between active conversations.

I think maybe it's still worth displaying somehow what the user will remember - IMHO that's last time they worked on something, more likely than when they first worked on something. 🤔

Or maybe it won't matter anymore, because titles will do.
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-23T02:51:54Z
user: login: tofarr
body: We probably need to remove those console.log statements, and will there be enough space on smaller screens - but aside from that LGTM.
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-03-23T17:16:09Z
user: login: tofarr
body: 🍰 
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-24T02:08:01Z

Review comments:
diff_hunk: @@ -205,7 +209,9 @@ export function ConversationCard({
             <ConversationRepoLink selectedRepository={selectedRepository} />
           )}
           <p className="text-xs text-neutral-400">
-            <time>{formatTimeDelta(new Date(lastUpdatedAt))} ago</time>
+            <time>
+              {formatTimeDelta(new Date(createdAt || lastUpdatedAt))} ago
path: frontend/src/components/features/conversation-panel/conversation-card.tsx
user: login: enyst
body: Why not the other way around?

I find lastUpdatedAt useful to figure out which conversation is which. 😅

createdAt doesn't seem too useful? Who is going to remember it and why?
created_at: 2025-03-23T01:41:49Z
author_association: COLLABORATOR
diff_hunk: @@ -205,7 +209,9 @@ export function ConversationCard({
             <ConversationRepoLink selectedRepository={selectedRepository} />
           )}
           <p className="text-xs text-neutral-400">
-            <time>{formatTimeDelta(new Date(lastUpdatedAt))} ago</time>
+            <time>
+              {formatTimeDelta(new Date(createdAt || lastUpdatedAt))} ago
path: frontend/src/components/features/conversation-panel/conversation-card.tsx
user: login: rbren
body: Maybe this is personal preference, but the list is sorted by createdAt, so it looks weird to me when the times show updatedAt. Everything looks out of order

We could also sort by updatedAt, but if you have two active conversations they're going to flip back and forth in slots 1 and 2, which would be a bad experience
created_at: 2025-03-23T01:46:06Z
author_association: COLLABORATOR
diff_hunk: @@ -205,7 +209,9 @@ export function ConversationCard({
             <ConversationRepoLink selectedRepository={selectedRepository} />
           )}
           <p className="text-xs text-neutral-400">
-            <time>{formatTimeDelta(new Date(lastUpdatedAt))} ago</time>
+            <time>
+              {formatTimeDelta(new Date(createdAt || lastUpdatedAt))} ago
path: frontend/src/components/features/conversation-panel/conversation-card.tsx
user: login: enyst
body: Afaik the conversations in the panel are ordered by lastUpdatedAt. So it seems to make sense to keep that visible, rather than the one doesn't matter.

Visually it would be strange to see conversations switch order for no visible reason. Maybe I'm missing something?
created_at: 2025-03-23T01:47:29Z
author_association: COLLABORATOR
diff_hunk: @@ -205,7 +209,9 @@ export function ConversationCard({
             <ConversationRepoLink selectedRepository={selectedRepository} />
           )}
           <p className="text-xs text-neutral-400">
-            <time>{formatTimeDelta(new Date(lastUpdatedAt))} ago</time>
+            <time>
+              {formatTimeDelta(new Date(createdAt || lastUpdatedAt))} ago
path: frontend/src/components/features/conversation-panel/conversation-card.tsx
user: login: enyst
body: Oh, I was just answering. I thought they're ordered by updatedAt, hmm
created_at: 2025-03-23T01:48:27Z
author_association: COLLABORATOR
diff_hunk: @@ -205,7 +209,9 @@ export function ConversationCard({
             <ConversationRepoLink selectedRepository={selectedRepository} />
           )}
           <p className="text-xs text-neutral-400">
-            <time>{formatTimeDelta(new Date(lastUpdatedAt))} ago</time>
+            <time>
+              {formatTimeDelta(new Date(createdAt || lastUpdatedAt))} ago
path: frontend/src/components/features/conversation-panel/conversation-card.tsx
user: login: enyst
body: It looks like you're right. There are some in my list "out of order" with what they display.
created_at: 2025-03-23T01:49:51Z
author_association: COLLABORATOR
diff_hunk: @@ -205,7 +209,9 @@ export function ConversationCard({
             <ConversationRepoLink selectedRepository={selectedRepository} />
           )}
           <p className="text-xs text-neutral-400">
-            <time>{formatTimeDelta(new Date(lastUpdatedAt))} ago</time>
+            <time>
+              {formatTimeDelta(new Date(createdAt || lastUpdatedAt))} ago
path: frontend/src/components/features/conversation-panel/conversation-card.tsx
user: login: enyst
body: Actually... I'm not sure this is right: 
> if you have two active conversations they're going to flip back and forth in slots 1 and 2, which would be a bad experience

UpdatedAt is a bad name, I remember it means ... something like last time the session was started? So they don't, and wouldn't, switch order (much)
created_at: 2025-03-23T01:53:06Z
author_association: COLLABORATOR
diff_hunk: @@ -205,7 +209,9 @@ export function ConversationCard({
             <ConversationRepoLink selectedRepository={selectedRepository} />
           )}
           <p className="text-xs text-neutral-400">
-            <time>{formatTimeDelta(new Date(lastUpdatedAt))} ago</time>
+            <time>
+              {formatTimeDelta(new Date(createdAt || lastUpdatedAt))} ago
path: frontend/src/components/features/conversation-panel/conversation-card.tsx
user: login: rbren
body: <img width="407" alt="Screenshot 2025-03-22 at 6 59 28 PM" src="https://github.com/user-attachments/assets/5f987c63-1187-4869-b7b4-e2247b4cbd1a" />
how about this?
created_at: 2025-03-23T01:59:41Z
author_association: COLLABORATOR
diff_hunk: @@ -205,7 +209,9 @@ export function ConversationCard({
             <ConversationRepoLink selectedRepository={selectedRepository} />
           )}
           <p className="text-xs text-neutral-400">
-            <time>{formatTimeDelta(new Date(lastUpdatedAt))} ago</time>
+            <time>
+              {formatTimeDelta(new Date(createdAt || lastUpdatedAt))} ago
path: frontend/src/components/features/conversation-panel/conversation-card.tsx
user: login: enyst
body: I think if updatedAt is the last time you opened the conversation, it makes sense to me to be what they're sorted on, and displayed. It won't flip back and forth for active conversations, their last session started when it started.

I could go with both if you want.
created_at: 2025-03-23T02:06:15Z
author_association: COLLABORATOR
diff_hunk: @@ -205,7 +209,9 @@ export function ConversationCard({
             <ConversationRepoLink selectedRepository={selectedRepository} />
           )}
           <p className="text-xs text-neutral-400">
-            <time>{formatTimeDelta(new Date(lastUpdatedAt))} ago</time>
+            <time>
+              {formatTimeDelta(new Date(createdAt || lastUpdatedAt))} ago
path: frontend/src/components/features/conversation-panel/conversation-card.tsx
user: login: enyst
body: Currently:

<img width="257" alt="image" src="https://github.com/user-attachments/assets/958537fc-9879-4522-a7b6-cc86d3bf9e44" />

created_at: 2025-03-23T02:11:46Z
author_association: COLLABORATOR
diff_hunk: @@ -205,7 +209,9 @@ export function ConversationCard({
             <ConversationRepoLink selectedRepository={selectedRepository} />
           )}
           <p className="text-xs text-neutral-400">
-            <time>{formatTimeDelta(new Date(lastUpdatedAt))} ago</time>
+            <time>
+              {formatTimeDelta(new Date(createdAt || lastUpdatedAt))} ago
path: frontend/src/components/features/conversation-panel/conversation-card.tsx
user: login: enyst
body: This sets updatedAt:
https://github.com/All-Hands-AI/OpenHands/blob/306188817f10c4e63f25c60d4cfaa92e18354aa6/openhands/server/conversation_manager/standalone_conversation_manager.py#L292

You're right. It would flip. 😓
created_at: 2025-03-23T02:22:38Z
author_association: COLLABORATOR

PR comments:
user: login: rbren
created_at: 2025-03-23T19:29:06Z
body: @openhands please make sure lint, build, and test all pass
author_association: COLLABORATOR
user: login: openhands-ai[bot]
created_at: 2025-03-23T19:29:14Z
body: I'm on it! @rbren can [track my progress at all-hands.dev](https://app.all-hands.dev/conversations/bee834875e0d4615b7c147d86b2e7e9e)
author_association: NONE
user: login: rbren
created_at: 2025-03-23T19:37:57Z
body: I have fixed the issues in the PR:

1. Fixed lint errors by removing console.log statements
2. Fixed build errors by handling the case where createdAt is undefined
3. Updated tests to match the new time format

All lint, build, and test checks are now passing.
author_association: COLLABORATOR