Skip to content

Commit 54c6273

Browse files
committed
Release v0.0.37
## What's New ### Improvements & Fixes - **Plan Mode UI** — Improved plan mode UI and sidebar refetch - **Auto-scroll Fix** — Allow user to disable auto-scroll during scroll animation - **Todo List Fix** — Fixed infinite loop in todo list useEffect
1 parent 7da405b commit 54c6273

File tree

14 files changed

+446
-331
lines changed

14 files changed

+446
-331
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "21st-desktop",
3-
"version": "0.0.36",
3+
"version": "0.0.37",
44
"private": true,
55
"description": "1Code - UI for parallel work with AI agents",
66
"author": {

src/main/lib/auto-updater.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,18 @@ export async function initAutoUpdater(getWindow: () => BrowserWindow | null) {
5252
initAutoUpdaterConfig()
5353

5454
// Configure feed URL to point to R2 CDN
55+
// Note: We use a custom request headers to bypass CDN cache
5556
autoUpdater.setFeedURL({
5657
provider: "generic",
5758
url: CDN_BASE,
5859
})
5960

61+
// Add cache-busting to update requests
62+
autoUpdater.requestHeaders = {
63+
"Cache-Control": "no-cache, no-store, must-revalidate",
64+
"Pragma": "no-cache",
65+
}
66+
6067
// Event: Checking for updates
6168
autoUpdater.on("checking-for-update", () => {
6269
log.info("[AutoUpdater] Checking for updates...")

src/main/lib/trpc/routers/chats.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1521,7 +1521,7 @@ export const chatsRouter = router({
15211521

15221522
/**
15231523
* Get sub-chats with pending plan approvals
1524-
* Parses messages to find ExitPlanMode tool calls without subsequent "Implement plan" user message
1524+
* Parses messages to find plan file Write without subsequent "Implement plan" user message
15251525
* Logic must match active-chat.tsx hasUnapprovedPlan
15261526
* REQUIRES openSubChatIds to avoid loading all sub-chats (performance optimization)
15271527
*/
@@ -1558,6 +1558,7 @@ export const chatsRouter = router({
15581558
parts?: Array<{
15591559
type: string
15601560
text?: string
1561+
output?: unknown
15611562
}>
15621563
}>
15631564

@@ -1578,10 +1579,13 @@ export const chatsRouter = router({
15781579
}
15791580
}
15801581

1581-
// If assistant message with ExitPlanMode that has output.plan, we found an unapproved plan
1582+
// If assistant message with completed ExitPlanMode, we found an unapproved plan
15821583
if (msg.role === "assistant" && msg.parts) {
1583-
const exitPlanPart = msg.parts.find((p) => p.type === "tool-ExitPlanMode") as { output?: { plan?: string } } | undefined
1584-
if (exitPlanPart?.output?.plan) {
1584+
const exitPlanPart = msg.parts.find(
1585+
(p) => p.type === "tool-ExitPlanMode"
1586+
)
1587+
// Check if ExitPlanMode is completed (has output, even if empty)
1588+
if (exitPlanPart && exitPlanPart.output !== undefined) {
15851589
return true
15861590
}
15871591
}

src/main/lib/trpc/routers/claude.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1190,6 +1190,10 @@ ${prompt}
11901190
let lastAssistantUuid: string | null = null
11911191
const streamIterationStart = Date.now()
11921192

1193+
// Plan mode: track ExitPlanMode to stop after plan is complete
1194+
let planCompleted = false
1195+
let exitPlanModeToolCallId: string | null = null
1196+
11931197
if (isUsingOllama) {
11941198
console.log(`[Ollama] ===== STARTING STREAM ITERATION =====`)
11951199
console.log(`[Ollama] Model: ${finalCustomConfig?.model}`)
@@ -1249,6 +1253,23 @@ ${prompt}
12491253
msgAny.error || msgAny.message || "Unknown SDK error"
12501254
lastError = new Error(sdkError)
12511255

1256+
// Detailed SDK error logging in main process
1257+
console.error(`[CLAUDE SDK ERROR] ========================================`)
1258+
console.error(`[CLAUDE SDK ERROR] Raw error: ${sdkError}`)
1259+
console.error(`[CLAUDE SDK ERROR] Message type: ${msgAny.type}`)
1260+
console.error(`[CLAUDE SDK ERROR] SubChat ID: ${input.subChatId}`)
1261+
console.error(`[CLAUDE SDK ERROR] Chat ID: ${input.chatId}`)
1262+
console.error(`[CLAUDE SDK ERROR] CWD: ${input.cwd}`)
1263+
console.error(`[CLAUDE SDK ERROR] Mode: ${input.mode}`)
1264+
console.error(`[CLAUDE SDK ERROR] Session ID: ${msgAny.session_id || 'none'}`)
1265+
console.error(`[CLAUDE SDK ERROR] Has custom config: ${!!finalCustomConfig}`)
1266+
console.error(`[CLAUDE SDK ERROR] Is using Ollama: ${isUsingOllama}`)
1267+
console.error(`[CLAUDE SDK ERROR] Model: ${resolvedModel || 'default'}`)
1268+
console.error(`[CLAUDE SDK ERROR] Has OAuth token: ${!!claudeCodeToken}`)
1269+
console.error(`[CLAUDE SDK ERROR] MCP servers: ${mcpServersFiltered ? Object.keys(mcpServersFiltered).join(', ') : 'none'}`)
1270+
console.error(`[CLAUDE SDK ERROR] Full message:`, JSON.stringify(msgAny, null, 2))
1271+
console.error(`[CLAUDE SDK ERROR] ========================================`)
1272+
12521273
// Categorize SDK-level errors
12531274
let errorCategory = "SDK_ERROR"
12541275
let errorContext = "Claude SDK error"
@@ -1381,6 +1402,12 @@ ${prompt}
13811402
// DEBUG: Log tool calls
13821403
console.log(`[SD] M:TOOL_CALL sub=${subId} toolName="${chunk.toolName}" mode=${input.mode} callId=${chunk.toolCallId}`)
13831404

1405+
// Track ExitPlanMode toolCallId so we can stop when it completes
1406+
if (input.mode === "plan" && chunk.toolName === "ExitPlanMode") {
1407+
console.log(`[SD] M:PLAN_TOOL_DETECTED sub=${subId} callId=${chunk.toolCallId}`)
1408+
exitPlanModeToolCallId = chunk.toolCallId
1409+
}
1410+
13841411
parts.push({
13851412
type: `tool-${chunk.toolName}`,
13861413
toolCallId: chunk.toolCallId,
@@ -1415,6 +1442,13 @@ ${prompt}
14151442
}
14161443
}
14171444
}
1445+
1446+
// Check if ExitPlanMode just completed - stop the stream
1447+
if (exitPlanModeToolCallId && chunk.toolCallId === exitPlanModeToolCallId) {
1448+
console.log(`[SD] M:PLAN_FINISH sub=${subId} - ExitPlanMode completed, emitting finish`)
1449+
planCompleted = true
1450+
safeEmit({ type: "finish" } as UIMessageChunk)
1451+
}
14181452
}
14191453
break
14201454
case "message-metadata":
@@ -1437,12 +1471,23 @@ ${prompt}
14371471
}
14381472
break
14391473
}
1474+
1475+
// Break from chunk loop if plan is done
1476+
if (planCompleted) {
1477+
console.log(`[SD] M:PLAN_BREAK_CHUNK sub=${subId}`)
1478+
break
1479+
}
14401480
}
14411481
// Break from stream loop if observer closed (user clicked Stop)
14421482
if (!isObservableActive) {
14431483
console.log(`[SD] M:OBSERVER_CLOSED_STREAM sub=${subId}`)
14441484
break
14451485
}
1486+
// Break from stream loop if plan completed
1487+
if (planCompleted) {
1488+
console.log(`[SD] M:PLAN_BREAK_STREAM sub=${subId}`)
1489+
break
1490+
}
14461491
}
14471492

14481493
// Warn if stream yielded no messages (offline mode issue)

src/renderer/components/dialogs/agents-settings-dialog.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ function useIsNarrowScreen(): boolean {
7070
return isNarrow
7171
}
7272

73-
// Check if we're in development mode
74-
const isDevelopment = process.env.NODE_ENV === "development"
73+
// Check if we're in development mode (use import.meta.env.DEV for Vite)
74+
const isDevelopment = import.meta.env.DEV
7575

7676
// Clicks required to unlock devtools in production
7777
const DEVTOOLS_UNLOCK_CLICKS = 5
@@ -307,9 +307,11 @@ export function AgentsSettingsDialog({
307307
}, [])
308308

309309
const handleTabClick = (tabId: SettingsTab) => {
310-
// Handle Beta tab clicks for devtools unlock (only in production builds)
311-
if (tabId === "beta" && !isDevelopment && !devToolsUnlocked) {
310+
// Handle Beta tab clicks for devtools unlock
311+
// Works in both dev and production - the unlock just reveals the Debug tab
312+
if (tabId === "beta" && !devToolsUnlocked) {
312313
betaClickCountRef.current++
314+
console.log(`[Settings] Beta click ${betaClickCountRef.current}/${DEVTOOLS_UNLOCK_CLICKS}`)
313315

314316
// Reset counter after 2 seconds of no clicks
315317
if (betaClickTimeoutRef.current) {

src/renderer/features/agents/atoms/index.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,3 +690,18 @@ export const currentPlanPathAtomFamily = atomFamily((chatId: string) =>
690690
},
691691
),
692692
)
693+
694+
// Per-chat plan edit refetch trigger - incremented when an Edit on a plan file completes
695+
// Used to trigger sidebar refetch when plan content changes
696+
const planEditRefetchTriggerStorageAtom = atom<Record<string, number>>({})
697+
698+
export const planEditRefetchTriggerAtomFamily = atomFamily((chatId: string) =>
699+
atom(
700+
(get) => get(planEditRefetchTriggerStorageAtom)[chatId] ?? 0,
701+
(get, set) => {
702+
const current = get(planEditRefetchTriggerStorageAtom)
703+
const currentValue = current[chatId] ?? 0
704+
set(planEditRefetchTriggerStorageAtom, { ...current, [chatId]: currentValue + 1 })
705+
},
706+
),
707+
)

src/renderer/features/agents/lib/ipc-chat-transport.ts

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ const ERROR_TOAST_CONFIG: Record<
109109
title: "Authentication failed",
110110
description: "Your session may have expired. Try logging in again.",
111111
},
112+
// SDK_ERROR is handled dynamically below to include full error details in copy
112113
}
113114

114115
type UIMessageChunk = any // Inferred from subscription
@@ -312,8 +313,23 @@ export class IPCChatTransport implements ChatTransport<UIMessage> {
312313

313314
// Handle errors - show toast to user FIRST before anything else
314315
if (chunk.type === "error") {
315-
// Track error in Sentry
316316
const category = chunk.debugInfo?.category || "UNKNOWN"
317+
318+
// Detailed SDK error logging for debugging
319+
console.error(`[SDK ERROR] ========================================`)
320+
console.error(`[SDK ERROR] Category: ${category}`)
321+
console.error(`[SDK ERROR] Error text: ${chunk.errorText}`)
322+
console.error(`[SDK ERROR] Chat ID: ${this.config.chatId}`)
323+
console.error(`[SDK ERROR] SubChat ID: ${this.config.subChatId}`)
324+
console.error(`[SDK ERROR] CWD: ${this.config.cwd}`)
325+
console.error(`[SDK ERROR] Mode: ${currentMode}`)
326+
if (chunk.debugInfo) {
327+
console.error(`[SDK ERROR] Debug info:`, JSON.stringify(chunk.debugInfo, null, 2))
328+
}
329+
console.error(`[SDK ERROR] Full chunk:`, JSON.stringify(chunk, null, 2))
330+
console.error(`[SDK ERROR] ========================================`)
331+
332+
// Track error in Sentry
317333
Sentry.captureException(
318334
new Error(chunk.errorText || "Claude transport error"),
319335
{
@@ -330,27 +346,34 @@ export class IPCChatTransport implements ChatTransport<UIMessage> {
330346
},
331347
)
332348

349+
// Build detailed error string for copying (available for ALL errors)
350+
const errorDetails = [
351+
`Error: ${chunk.errorText || "Unknown error"}`,
352+
`Category: ${category}`,
353+
`Chat ID: ${this.config.chatId}`,
354+
`SubChat ID: ${this.config.subChatId}`,
355+
`CWD: ${this.config.cwd}`,
356+
`Mode: ${currentMode}`,
357+
`Timestamp: ${new Date().toISOString()}`,
358+
chunk.debugInfo ? `Debug Info: ${JSON.stringify(chunk.debugInfo, null, 2)}` : null,
359+
].filter(Boolean).join("\n")
360+
333361
// Show toast based on error category
334362
const config = ERROR_TOAST_CONFIG[category]
335-
336-
if (config) {
337-
toast.error(config.title, {
338-
description: config.description,
339-
duration: 8000,
340-
action: config.action
341-
? {
342-
label: config.action.label,
343-
onClick: config.action.onClick,
344-
}
345-
: undefined,
346-
})
347-
} else {
348-
toast.error("Something went wrong", {
349-
description:
350-
chunk.errorText || "An unexpected error occurred",
351-
duration: 8000,
352-
})
353-
}
363+
const title = config?.title || "Claude error"
364+
const description = config?.description || chunk.errorText || "An unexpected error occurred"
365+
366+
toast.error(title, {
367+
description,
368+
duration: 12000,
369+
action: {
370+
label: "Copy Error",
371+
onClick: () => {
372+
navigator.clipboard.writeText(errorDetails)
373+
toast.success("Error details copied to clipboard")
374+
},
375+
},
376+
})
354377
}
355378

356379
// Try to enqueue, but don't crash if stream is already closed

0 commit comments

Comments
 (0)