Skip to content

Commit f19742e

Browse files
voidcraft-devclaude
andcommitted
feat: batch archive/favorite — multi-select sessions with Shift range and ESC exit
Backend - database.rs: batch_set_session_flag — single transaction, INSERT OR IGNORE / DELETE - session_service.rs: thin wrapper with perf log - main.rs: register session_batch_set_flag command Frontend - api.ts: api.batchSetFlag(platform, keys, flag, set) - i18n.ts: 16 new strings (zh/en) for selection toolbar - session-list.tsx: - top "multi-select" toggle button next to refresh - blue toolbar strip with selectedCount / select all / invert / archive / favorite / exit - per-card checkbox indicator (top-right) when in selection mode - Shift+Click range select; ESC exits - auto-exit on platform / view switch - hides single-card favorite/archive icons during selection (avoids misclick) Bumped version 3.0.11 -> 3.1.0 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent d4a50df commit f19742e

10 files changed

Lines changed: 343 additions & 43 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "memory-forge-v3",
3-
"version": "3.0.11",
3+
"version": "3.1.0",
44
"description": "Stop resetting. Start editing. — Local AI session manager for Claude Code, Codex CLI, OpenCode & Kiro CLI",
55
"type": "module",
66
"scripts": {

src-tauri/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "memory-forge"
3-
version = "3.0.11"
3+
version = "3.1.0"
44
description = "Memory Forge — Local AI Session Manager"
55
authors = ["VoidCraft"]
66
edition = "2021"

src-tauri/src/database.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,41 @@ pub fn toggle_session_flag(
124124
}
125125
}
126126

127+
/// Set or unset a flag on multiple sessions in a single transaction.
128+
/// Returns the number of rows affected (inserted when `set`=true, deleted when `set`=false).
129+
pub fn batch_set_session_flag(
130+
conn: &Mutex<Connection>,
131+
platform: &str,
132+
session_keys: &[String],
133+
flag: &str,
134+
set: bool,
135+
) -> Result<usize, String> {
136+
if session_keys.is_empty() {
137+
return Ok(0);
138+
}
139+
let mut conn = conn.lock().map_err(|e| format!("DB lock error: {e}"))?;
140+
let tx = conn.transaction().map_err(|e| format!("Begin tx error: {e}"))?;
141+
let mut affected = 0usize;
142+
for key in session_keys {
143+
let n = if set {
144+
tx.execute(
145+
"INSERT OR IGNORE INTO session_flags (platform, session_key, flag) VALUES (?1, ?2, ?3)",
146+
params![platform, key, flag],
147+
)
148+
.map_err(|e| format!("Batch insert error: {e}"))?
149+
} else {
150+
tx.execute(
151+
"DELETE FROM session_flags WHERE platform = ?1 AND session_key = ?2 AND flag = ?3",
152+
params![platform, key, flag],
153+
)
154+
.map_err(|e| format!("Batch delete error: {e}"))?
155+
};
156+
affected += n;
157+
}
158+
tx.commit().map_err(|e| format!("Commit error: {e}"))?;
159+
Ok(affected)
160+
}
161+
127162
/// Get all session_keys that have a specific flag.
128163
pub fn get_flagged_keys(
129164
conn: &Mutex<Connection>,

src-tauri/src/main.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,17 @@ fn session_toggle_flag(
100100
session_service::session_toggle_flag(&db, &platform, &session_key, &flag)
101101
}
102102

103+
#[tauri::command]
104+
fn session_batch_set_flag(
105+
db: tauri::State<'_, DbState>,
106+
platform: String,
107+
session_keys: Vec<String>,
108+
flag: String,
109+
set: bool,
110+
) -> Result<usize, String> {
111+
session_service::session_batch_set_flag(&db, &platform, &session_keys, &flag, set)
112+
}
113+
103114
#[tauri::command]
104115
fn session_edit_message(
105116
db: tauri::State<'_, DbState>,
@@ -239,6 +250,7 @@ fn main() {
239250
session_detail,
240251
session_set_alias,
241252
session_toggle_flag,
253+
session_batch_set_flag,
242254
session_edit_message,
243255
session_edit_log,
244256
session_restore_message,

src-tauri/src/session_service.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,19 @@ pub fn session_toggle_flag(
199199
database::toggle_session_flag(&db.conn, platform, session_key, flag)
200200
}
201201

202+
pub fn session_batch_set_flag(
203+
db: &DbState,
204+
platform: &str,
205+
session_keys: &[String],
206+
flag: &str,
207+
set: bool,
208+
) -> Result<usize, String> {
209+
let t0 = Instant::now();
210+
let affected = database::batch_set_session_flag(&db.conn, platform, session_keys, flag, set)?;
211+
eprintln!("[perf] session_batch_set_flag({platform}, {flag}, set={set}) {} keys -> {} affected: {:?}", session_keys.len(), affected, t0.elapsed());
212+
Ok(affected)
213+
}
214+
202215
pub fn session_detail(db: &DbState, settings: &AppSettings, platform: &str, session_key: &str) -> Result<SessionDetail, String> {
203216
let t0 = Instant::now();
204217
let adapter = platforms::get_adapter(platform, settings)?;

src-tauri/tauri.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"devUrl": "http://localhost:1430"
77
},
88
"productName": "Memory Forge",
9-
"version": "3.0.11",
9+
"version": "3.1.0",
1010
"identifier": "com.voidcraft.memoryforge",
1111
"bundle": {
1212
"active": true,

src/features/desktop/api.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,13 @@ export const api = {
330330
return false;
331331
},
332332

333+
async batchSetFlag(platform: string, sessionKeys: string[], flag: string, set: boolean): Promise<number> {
334+
if (isTauriRuntime()) {
335+
return invoke<number>("session_batch_set_flag", { platform, sessionKeys, flag, set });
336+
}
337+
return 0;
338+
},
339+
333340
async checkUpdate(): Promise<UpdateInfo> {
334341
if (isTauriRuntime()) {
335342
return invoke<UpdateInfo>("check_update");

src/features/desktop/i18n.ts

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,22 @@ export type MessageKey =
177177
| "session.archiveView"
178178
| "session.sessionsView"
179179
| "session.noArchivedSessions"
180-
| "session.archiveConfirm";
180+
| "session.archiveConfirm"
181+
| "session.selectMode"
182+
| "session.exitSelect"
183+
| "session.selectAll"
184+
| "session.invertSelection"
185+
| "session.selectedCount"
186+
| "session.batchArchive"
187+
| "session.batchUnarchive"
188+
| "session.batchFavorite"
189+
| "session.batchUnfavorite"
190+
| "session.batchArchived"
191+
| "session.batchUnarchived"
192+
| "session.batchFavorited"
193+
| "session.batchUnfavorited"
194+
| "session.batchFailed"
195+
| "session.noSelection";
181196

182197
const messages: Record<LocaleId, Record<MessageKey, string>> = {
183198
"zh-CN": {
@@ -348,6 +363,21 @@ const messages: Record<LocaleId, Record<MessageKey, string>> = {
348363
"session.sessionsView": "会话",
349364
"session.noArchivedSessions": "暂无归档会话",
350365
"session.archiveConfirm": "确定归档这个会话吗?归档后不会在列表中显示,可以在归档视图中恢复。",
366+
"session.selectMode": "多选",
367+
"session.exitSelect": "退出多选",
368+
"session.selectAll": "全选",
369+
"session.invertSelection": "反选",
370+
"session.selectedCount": "已选 {count}",
371+
"session.batchArchive": "批量归档",
372+
"session.batchUnarchive": "批量取消归档",
373+
"session.batchFavorite": "批量收藏",
374+
"session.batchUnfavorite": "批量取消收藏",
375+
"session.batchArchived": "已归档 {count} 个会话",
376+
"session.batchUnarchived": "已恢复 {count} 个会话",
377+
"session.batchFavorited": "已收藏 {count} 个会话",
378+
"session.batchUnfavorited": "已取消收藏 {count} 个会话",
379+
"session.batchFailed": "批量操作失败",
380+
"session.noSelection": "未选中任何会话",
351381
},
352382
en: {
353383
appName: "Memory Forge",
@@ -517,6 +547,21 @@ const messages: Record<LocaleId, Record<MessageKey, string>> = {
517547
"session.sessionsView": "Sessions",
518548
"session.noArchivedSessions": "No archived sessions",
519549
"session.archiveConfirm": "Archive this session? It will be hidden from the list but can be restored from the archive view.",
550+
"session.selectMode": "Select",
551+
"session.exitSelect": "Exit",
552+
"session.selectAll": "Select all",
553+
"session.invertSelection": "Invert",
554+
"session.selectedCount": "{count} selected",
555+
"session.batchArchive": "Archive",
556+
"session.batchUnarchive": "Unarchive",
557+
"session.batchFavorite": "Favorite",
558+
"session.batchUnfavorite": "Unfavorite",
559+
"session.batchArchived": "Archived {count} session(s)",
560+
"session.batchUnarchived": "Restored {count} session(s)",
561+
"session.batchFavorited": "Favorited {count} session(s)",
562+
"session.batchUnfavorited": "Unfavorited {count} session(s)",
563+
"session.batchFailed": "Batch operation failed",
564+
"session.noSelection": "No sessions selected",
520565
},
521566
};
522567

0 commit comments

Comments
 (0)