Skip to content

Commit 0b1f820

Browse files
committed
Release v0.0.33
**Full Changelog**: 21st-dev/21st@v0.0.32...v0.0.33
1 parent 2e2aad8 commit 0b1f820

File tree

5 files changed

+80
-75
lines changed

5 files changed

+80
-75
lines changed

bun.lockb

65.3 KB
Binary file not shown.

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.32",
3+
"version": "0.0.33",
44
"private": true,
55
"description": "1Code - UI for parallel work with AI agents",
66
"author": {

src/renderer/components/dialogs/settings-tabs/agents-preferences-tab.tsx

Lines changed: 24 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -113,12 +113,7 @@ export function AgentsPreferencesTab() {
113113
</div>
114114
<Switch checked={soundEnabled} onCheckedChange={setSoundEnabled} />
115115
</div>
116-
</div>
117-
</div>
118116

119-
{/* Git Section */}
120-
<div className="bg-background rounded-lg border border-border overflow-hidden">
121-
<div className="p-4 space-y-6">
122117
{/* Co-Authored-By Toggle */}
123118
<div className="flex items-start justify-between">
124119
<div className="flex flex-col space-y-1">
@@ -135,35 +130,32 @@ export function AgentsPreferencesTab() {
135130
disabled={setCoAuthoredByMutation.isPending}
136131
/>
137132
</div>
138-
</div>
139-
</div>
140-
141-
{/* Keyboard Shortcuts Section */}
142-
<div className="bg-background rounded-lg border border-border overflow-hidden">
143-
<div className="flex items-start justify-between p-4">
144-
<div className="flex flex-col space-y-1">
145-
<span className="text-sm font-medium text-foreground">
146-
Quick Switch
147-
</span>
148-
<span className="text-xs text-muted-foreground">
149-
What <Kbd>⌃Tab</Kbd> switches between
150-
</span>
151-
</div>
152133

153-
<Select
154-
value={ctrlTabTarget}
155-
onValueChange={(value: CtrlTabTarget) => setCtrlTabTarget(value)}
156-
>
157-
<SelectTrigger className="w-auto px-2">
158-
<span className="text-xs">
159-
{ctrlTabTarget === "workspaces" ? "Workspaces" : "Agents"}
134+
{/* Quick Switch */}
135+
<div className="flex items-start justify-between">
136+
<div className="flex flex-col space-y-1">
137+
<span className="text-sm font-medium text-foreground">
138+
Quick Switch
139+
</span>
140+
<span className="text-xs text-muted-foreground">
141+
What <Kbd>⌃Tab</Kbd> switches between
160142
</span>
161-
</SelectTrigger>
162-
<SelectContent>
163-
<SelectItem value="workspaces">Workspaces</SelectItem>
164-
<SelectItem value="agents">Agents</SelectItem>
165-
</SelectContent>
166-
</Select>
143+
</div>
144+
<Select
145+
value={ctrlTabTarget}
146+
onValueChange={(value: CtrlTabTarget) => setCtrlTabTarget(value)}
147+
>
148+
<SelectTrigger className="w-auto px-2">
149+
<span className="text-xs">
150+
{ctrlTabTarget === "workspaces" ? "Workspaces" : "Agents"}
151+
</span>
152+
</SelectTrigger>
153+
<SelectContent>
154+
<SelectItem value="workspaces">Workspaces</SelectItem>
155+
<SelectItem value="agents">Agents</SelectItem>
156+
</SelectContent>
157+
</Select>
158+
</div>
167159
</div>
168160
</div>
169161

src/renderer/features/agents/ui/agent-todo-tool.tsx

Lines changed: 55 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -329,11 +329,30 @@ export const AgentTodoTool = memo(function AgentTodoTool({
329329
const rawOldTodos = part.output?.oldTodos || []
330330
const newTodos = part.input?.todos || part.output?.newTodos || []
331331

332+
// Check if we're still streaming input (data not yet complete)
333+
const isStreaming = part.state === "input-streaming"
334+
332335
// Determine if this is the creation tool call
333336
// A tool call is the "creation" if:
334337
// 1. It's the first tool call (creationToolCallId is null) OR
335338
// 2. It matches the stored creationToolCallId
336-
const isCreationToolCall = creationToolCallId === null || creationToolCallId === part.toolCallId
339+
// 3. NEW: This is a new generation - detected when:
340+
// - output.oldTodos explicitly exists and is empty (server confirmed this is a new list)
341+
// - we have newTodos (creation always has new todos)
342+
// - there are existing syncedTodos from previous generation
343+
// - this is a different tool call than the stored creation one
344+
// IMPORTANT: Check if output.oldTodos is explicitly an empty array, not just missing
345+
// If output doesn't exist yet or oldTodos is undefined, we can't determine if it's new generation
346+
const hasOutputWithEmptyOldTodos = part.output !== undefined &&
347+
'oldTodos' in part.output &&
348+
Array.isArray(part.output.oldTodos) &&
349+
part.output.oldTodos.length === 0
350+
const isNewGeneration = hasOutputWithEmptyOldTodos &&
351+
newTodos.length > 0 &&
352+
syncedTodos.length > 0 &&
353+
creationToolCallId !== null &&
354+
creationToolCallId !== part.toolCallId
355+
const isCreationToolCall = creationToolCallId === null || creationToolCallId === part.toolCallId || isNewGeneration
337356

338357
// Use syncedTodos as fallback for oldTodos when output hasn't arrived yet
339358
// This prevents flickering: without this, when a new tool call arrives with
@@ -398,37 +417,61 @@ export const AgentTodoTool = memo(function AgentTodoTool({
398417
// During streaming, JSON parsing may return partial arrays, causing temporary drops in length
399418
const shouldUpdate = isCreationToolCall || newTodos.length >= currentSyncedTodos.length
400419

420+
// If this is a new generation, reset the creationToolCallId to this tool call
421+
const newCreationId = isNewGeneration ? part.toolCallId : (creationToolCallId === null ? part.toolCallId : creationToolCallId)
422+
401423
if (shouldUpdate) {
402424
// Prevent infinite loop: check if todos actually changed before updating
403425
// Compare by serializing to JSON - if content is the same, skip update
404426
const newTodosJson = JSON.stringify(newTodos)
405427
const syncedTodosJson = JSON.stringify(currentSyncedTodos)
406428

407429
if (newTodosJson !== syncedTodosJson) {
408-
const newCreationId = creationToolCallId === null ? part.toolCallId : creationToolCallId
409430
setTodoState({ todos: newTodos, creationToolCallId: newCreationId })
410431
}
411432
}
412433
}
413-
}, [newTodos, setTodoState, creationToolCallId, part.toolCallId, isCreationToolCall])
434+
}, [newTodos, setTodoState, creationToolCallId, part.toolCallId, isCreationToolCall, isNewGeneration])
414435

415-
// Check if we're still streaming input (data not yet complete)
416-
const isStreaming = part.state === "input-streaming"
436+
// For UPDATE tool calls while streaming, show "Updating..." placeholder
437+
// This check MUST come BEFORE the newTodos.length === 0 check
438+
// Otherwise we return null when newTodos is empty during streaming updates
439+
if (!isCreationToolCall && isStreaming) {
440+
return (
441+
<div className="flex items-start gap-1.5 py-0.5 rounded-md px-2">
442+
<div className="flex-1 min-w-0 flex items-center gap-1.5">
443+
<div className="text-xs text-muted-foreground flex items-center gap-1.5 min-w-0">
444+
<span className="font-medium whitespace-nowrap flex-shrink-0">
445+
<TextShimmer
446+
as="span"
447+
duration={1.2}
448+
className="inline-flex items-center text-xs leading-none h-4 m-0"
449+
>
450+
Updating to-dos...
451+
</TextShimmer>
452+
</span>
453+
</div>
454+
</div>
455+
</div>
456+
)
457+
}
417458

418-
// Early streaming state - show placeholder
459+
// Early streaming state - show placeholder for CREATION only
419460
if (
420461
newTodos.length === 0 ||
421462
(isStreaming && !part.input?.todos)
422463
) {
423464
// For update tool calls (not creation), return null to avoid showing placeholder
465+
// Note: This branch is only reached when !isStreaming (update streaming handled above)
424466
if (!isCreationToolCall) {
425467
return null
426468
}
427469

428470
// For creation tool calls, show the placeholder - also sticky with top offset
471+
// z-[5] ensures todo stays below user message (z-10) when both are sticky
429472
return (
430473
<div
431-
className="mx-2 sticky bg-background"
474+
className="mx-2 sticky z-[5] bg-background"
432475
style={{ top: 'calc(var(--user-message-height, 28px) - 29px)' }}
433476
>
434477
<div className="rounded-lg border border-border bg-muted/30 px-2.5 py-1.5">
@@ -453,28 +496,6 @@ export const AgentTodoTool = memo(function AgentTodoTool({
453496
)
454497
}
455498

456-
// For UPDATE tool calls while streaming, show "Updating..." placeholder
457-
// This prevents showing intermediate/incorrect states during streaming
458-
if (!isCreationToolCall && isStreaming) {
459-
return (
460-
<div className="flex items-start gap-1.5 py-0.5 rounded-md px-2">
461-
<div className="flex-1 min-w-0 flex items-center gap-1.5">
462-
<div className="text-xs text-muted-foreground flex items-center gap-1.5 min-w-0">
463-
<span className="font-medium whitespace-nowrap flex-shrink-0">
464-
<TextShimmer
465-
as="span"
466-
duration={1.2}
467-
className="inline-flex items-center text-xs leading-none h-4 m-0"
468-
>
469-
Updating todos...
470-
</TextShimmer>
471-
</span>
472-
</div>
473-
</div>
474-
</div>
475-
)
476-
}
477-
478499
// COMPACT MODE: Single update - render as simple tool call
479500
if (changes.type === "single") {
480501
const change = changes.items[0]
@@ -508,7 +529,7 @@ export const AgentTodoTool = memo(function AgentTodoTool({
508529
).length
509530

510531
// Build summary title
511-
let summaryTitle = "Updated todos"
532+
let summaryTitle = "Updated to-dos"
512533
if (completedChanges > 0 && startedChanges === 0) {
513534
summaryTitle = `Finished ${completedChanges} ${completedChanges === 1 ? "task" : "tasks"}`
514535
} else if (startedChanges > 0 && completedChanges === 0) {
@@ -581,7 +602,8 @@ export const AgentTodoTool = memo(function AgentTodoTool({
581602
className={cn(
582603
"mx-2",
583604
// Make entire creation todo sticky
584-
isCreationToolCall && "sticky bg-background"
605+
// z-[5] ensures todo stays below user message (z-10) when both are sticky
606+
isCreationToolCall && "sticky z-[5] bg-background"
585607
)}
586608
style={isCreationToolCall ? {
587609
// Offset so TOP BLOCK (title) goes fully under user message
@@ -595,7 +617,7 @@ export const AgentTodoTool = memo(function AgentTodoTool({
595617
onClick={handleToggleExpand}
596618
role="button"
597619
aria-expanded={isExpanded}
598-
aria-label={`Todo list with ${totalTodos} items. Click to ${isExpanded ? "collapse" : "expand"}`}
620+
aria-label={`To-do list with ${totalTodos} items. Click to ${isExpanded ? "collapse" : "expand"}`}
599621
tabIndex={0}
600622
onKeyDown={handleKeyDown}
601623
>
@@ -605,7 +627,7 @@ export const AgentTodoTool = memo(function AgentTodoTool({
605627
To-dos
606628
</span>
607629
<span className="text-xs text-muted-foreground truncate flex-1">
608-
{displayTodos[0]?.content || "Todo List"}
630+
{displayTodos[0]?.content || "To-do list"}
609631
</span>
610632
{/* Expand/Collapse icon */}
611633
<div className="relative w-4 h-4 flex-shrink-0">

src/renderer/lib/atoms/index.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -384,15 +384,6 @@ export const analyticsOptOutAtom = atomWithStorage<boolean>(
384384
{ getOnInit: true },
385385
)
386386

387-
// Preferences - Disable Co-Authored-By Attribution
388-
// When true, Claude will not add "Co-authored-by: Claude" to git commits
389-
export const disableCoAuthoredByAtom = atomWithStorage<boolean>(
390-
"preferences:disable-coauthored-by",
391-
false, // Default to false (keep co-authored-by attribution)
392-
undefined,
393-
{ getOnInit: true },
394-
)
395-
396387
// Beta: Enable git features in diff sidebar (commit, staging, file selection)
397388
// When enabled, shows checkboxes for file selection and commit UI in diff sidebar
398389
// When disabled, shows simple file list with "Create PR" button

0 commit comments

Comments
 (0)