feat: surface memory file last-modified timestamps in MemoryBrowserPanel (#230)

Extends MemoryFileInfo with last_modified (Unix timestamp) populated via
fs::metadata on native platforms, and displays a formatted date in the
file list as secondary text.
This commit is contained in:
2026-03-20 10:19:27 -07:00
committed by Naomi Carrigan
parent 3f248f7ffa
commit 6945ebc998
2 changed files with 51 additions and 5 deletions
+17 -3
View File
@@ -1464,6 +1464,7 @@ pub async fn close_application(app_handle: AppHandle) -> Result<(), String> {
pub struct MemoryFileInfo { pub struct MemoryFileInfo {
pub path: String, pub path: String,
pub heading: Option<String>, pub heading: Option<String>,
pub last_modified: Option<String>, // Unix timestamp in seconds as a string
} }
#[derive(serde::Serialize)] #[derive(serde::Serialize)]
@@ -1535,7 +1536,11 @@ async fn list_memory_files_via_wsl() -> Result<MemoryFilesResponse, String> {
let mut files = Vec::new(); let mut files = Vec::new();
for path in paths { for path in paths {
let heading = read_wsl_file_first_heading(&path); let heading = read_wsl_file_first_heading(&path);
files.push(MemoryFileInfo { path, heading }); files.push(MemoryFileInfo {
path,
heading,
last_modified: None,
});
} }
Ok(MemoryFilesResponse { files }) Ok(MemoryFilesResponse { files })
@@ -1605,14 +1610,23 @@ async fn list_memory_files_native() -> Result<MemoryFilesResponse, String> {
// Sort files alphabetically // Sort files alphabetically
memory_paths.sort(); memory_paths.sort();
// Read first heading from each file // Read first heading and modification time from each file
let files = memory_paths let files = memory_paths
.into_iter() .into_iter()
.map(|path| { .map(|path| {
let heading = fs::read_to_string(&path) let heading = fs::read_to_string(&path)
.ok() .ok()
.and_then(|content| extract_first_heading(&content)); .and_then(|content| extract_first_heading(&content));
MemoryFileInfo { path, heading } let last_modified = fs::metadata(&path)
.ok()
.and_then(|m| m.modified().ok())
.and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
.map(|d| d.as_secs().to_string());
MemoryFileInfo {
path,
heading,
last_modified,
}
}) })
.collect(); .collect();
+34 -2
View File
@@ -14,6 +14,7 @@
interface MemoryFileInfo { interface MemoryFileInfo {
path: string; path: string;
heading: string | null; heading: string | null;
last_modified?: string; // Unix timestamp in seconds as a string, optional for backwards compat
} }
interface MemoryFilesResponse { interface MemoryFilesResponse {
@@ -65,6 +66,16 @@
return file.heading ?? getFileName(file.path); return file.heading ?? getFileName(file.path);
} }
function formatLastModified(ts: string | undefined): string {
if (!ts) return "";
const date = new Date(Number(ts) * 1000);
return date.toLocaleDateString(undefined, {
year: "numeric",
month: "short",
day: "numeric",
});
}
async function sendMemoryCommand() { async function sendMemoryCommand() {
const conversationId = get(claudeStore.activeConversationId); const conversationId = get(claudeStore.activeConversationId);
if (!conversationId) return; if (!conversationId) return;
@@ -205,7 +216,12 @@
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
/> />
</svg> </svg>
<span class="file-name">{getDisplayName(file)}</span> <div class="file-info">
<span class="file-name">{getDisplayName(file)}</span>
{#if file.last_modified}
<span class="file-date">{formatLastModified(file.last_modified)}</span>
{/if}
</div>
</button> </button>
{/each} {/each}
</div> </div>
@@ -416,7 +432,7 @@
.file-item { .file-item {
display: flex; display: flex;
align-items: center; align-items: flex-start;
gap: 0.75rem; gap: 0.75rem;
padding: 0.75rem 1rem; padding: 0.75rem 1rem;
background: var(--bg-secondary); background: var(--bg-secondary);
@@ -445,6 +461,13 @@
flex-shrink: 0; flex-shrink: 0;
} }
.file-info {
display: flex;
flex-direction: column;
gap: 0.125rem;
overflow: hidden;
}
.file-name { .file-name {
font-size: 0.875rem; font-size: 0.875rem;
font-weight: 500; font-weight: 500;
@@ -453,6 +476,15 @@
white-space: nowrap; white-space: nowrap;
} }
.file-date {
font-size: 0.75rem;
color: var(--text-secondary);
}
.file-item.active .file-date {
color: rgba(255, 255, 255, 0.75);
}
.file-viewer { .file-viewer {
display: flex; display: flex;
flex-direction: column; flex-direction: column;