feat: task persistence, memoized UI components, hook/delete-failure safety
locode-authored (dogfooded) changes:
- TaskStore.toJSON/fromJSON: task_create/list state now survives session
resume instead of resetting to empty (session.ts, task.ts, sessionStore.ts).
- StatusBar/ChatInput/ThinkingIndicator wrapped in memo() to skip re-renders
during the ~30fps streaming-text update loop.
- HistoryItemView's todos list keys off t.content instead of array index
(TodoItem has no id field; content is the best stable key available).
- sessionStore.ts read paths now log (console.warn) on corrupt/unreadable
files instead of silently swallowing the error.
Fixes on top, found in review:
- safeRunHooksForEvent() wraps the 4 remaining unprotected hook call sites
(PreToolUse, PermissionRequest, PostToolUse, SubagentStart) — these fire on
every tool call/sub-agent, far more often than the 3 sites already wrapped
(Stop/SessionStart/UserPromptSubmit), so a broken hook script was still able
to abort an in-flight turn.
- deleteSession() no longer reports success when unlinkSync fails for a
reason other than ENOENT (e.g. EPERM/EBUSY from a Windows file lock) — it
used to drop the index entry and return true anyway, orphaning the file on
disk with no way to reference it again. `sessions rm` in cli.ts now
distinguishes "not found" from "found but couldn't delete" in its message.
- ChatInput's memo() was a no-op: onSubmit={handleSubmit} passed a fresh
closure every render since handleSubmit isn't wrapped in useCallback.
Rather than force a ~300-line function (which reads isThinking/streamingText
— both changing every streaming frame) into a dependency array, added a
handleSubmitRef + stable wrapper, the same ref-indirection this file
already uses for phaseRef.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LxiAaGhSD4DRVYYQZ5GJjm
This commit is contained in:
+6
-1
@@ -9,7 +9,7 @@ import { configFilePath, loadStoredConfig, saveStoredConfig, type StoredConfig }
|
||||
import { loadMergedHooks, userHooksFilePath } from "./hooks/config.js";
|
||||
import { loadMergedServers, removeUserServer, saveUserServer, userMcpFilePath } from "./mcp/config.js";
|
||||
import { isHttpServerConfig } from "./mcp/types.js";
|
||||
import { deleteSession, listSessions, mostRecentSessionId, sessionsDir } from "./persistence/sessionStore.js";
|
||||
import { deleteSession, listSessions, loadSession, mostRecentSessionId, sessionsDir } from "./persistence/sessionStore.js";
|
||||
import { addInstalledPlugin, loadInstalledPlugins, pluginsDir, removeInstalledPlugin } from "./plugins/config.js";
|
||||
import { loadPlugin } from "./plugins/loader.js";
|
||||
import { runInkApp } from "./ui/ink/index.js";
|
||||
@@ -230,6 +230,11 @@ sessionsCmd
|
||||
.action((id: string) => {
|
||||
if (deleteSession(id)) {
|
||||
console.log(`Deleted session ${id}.`);
|
||||
} else if (loadSession(id)) {
|
||||
// The file exists but deleteSession() couldn't actually remove it (e.g. locked by another
|
||||
// process) — a different situation from "no such session", so say so distinctly.
|
||||
console.error(`Could not delete session "${id}" — the file may be in use by another process.`);
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.error(`No saved session found with id "${id}".`);
|
||||
process.exit(1);
|
||||
|
||||
Reference in New Issue
Block a user