Skip to content

Commit 783fe43

Browse files
committed
Release v0.0.57
## What's New ### Features - **Git Activity Badges** — Show git activity badges on agent messages ### Improvements & Fixes - **Status Card** — Hide expand chevron when no files to show - **Git Modal** — Fixed crash after git modal close - **Git Pull** — Fixed git pull functionality - **Env Config** — Fixed missing comma in env assignment
1 parent c775621 commit 783fe43

15 files changed

Lines changed: 480 additions & 52 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": "21st-desktop",
3-
"version": "0.0.55",
3+
"version": "0.0.57",
44
"private": true,
55
"description": "1Code - UI for parallel work with AI agents",
66
"author": {

src/main/lib/git/git-operations.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { publicProcedure, router } from "../trpc";
55
import { isUpstreamMissingError } from "./git-utils";
66
import { assertRegisteredWorktree } from "./security";
77
import { fetchGitHubPRStatus } from "./github";
8+
import { gitCache } from "./cache";
89
import {
910
createGit,
1011
createGitForNetwork,
@@ -31,6 +32,12 @@ async function hasUpstreamBranch(
3132
/** Protected branches that should not be force-pushed to */
3233
const PROTECTED_BRANCHES = ["main", "master", "develop", "production", "staging"];
3334

35+
function invalidateGitStateCaches(worktreePath: string): void {
36+
gitCache.invalidateStatus(worktreePath);
37+
gitCache.invalidateParsedDiff(worktreePath);
38+
gitCache.invalidateAllFileContents(worktreePath);
39+
}
40+
3441
export const createGitOperationsRouter = () => {
3542
return router({
3643
// NOTE: saveFile is defined in file-contents.ts with hardened path validation
@@ -50,6 +57,7 @@ export const createGitOperationsRouter = () => {
5057
await withLockRetry(input.worktreePath, () =>
5158
git.fetch(["--all", "--prune"])
5259
);
60+
invalidateGitStateCaches(input.worktreePath);
5361
return { success: true };
5462
});
5563
}),
@@ -76,6 +84,7 @@ export const createGitOperationsRouter = () => {
7684
await withLockRetry(input.worktreePath, () =>
7785
git.checkout(input.branch)
7886
);
87+
invalidateGitStateCaches(input.worktreePath);
7988
return { success: true };
8089
});
8190
}),
@@ -98,6 +107,7 @@ export const createGitOperationsRouter = () => {
98107
author: string;
99108
email: string;
100109
date: Date;
110+
tags: string[];
101111
}>
102112
> => {
103113
assertRegisteredWorktree(input.worktreePath);
@@ -106,7 +116,7 @@ export const createGitOperationsRouter = () => {
106116
const logOutput = await git.raw([
107117
"log",
108118
`-${input.limit}`,
109-
"--format=%H|%h|%s|%an|%ae|%aI",
119+
"--format=%H|%h|%s|%an|%ae|%aI|%D",
110120
]);
111121

112122
if (!logOutput.trim()) return [];
@@ -115,15 +125,22 @@ export const createGitOperationsRouter = () => {
115125
.trim()
116126
.split("\n")
117127
.map((line) => {
118-
const [hash, shortHash, message, author, email, dateStr] =
128+
const [hash, shortHash, message, author, email, dateStr, refs] =
119129
line.split("|");
130+
// Extract tags from ref names (format: "HEAD -> main, tag: v1.0.0, origin/main")
131+
const tags = (refs || "")
132+
.split(",")
133+
.map((r) => r.trim())
134+
.filter((r) => r.startsWith("tag: "))
135+
.map((r) => r.replace("tag: ", ""));
120136
return {
121137
hash: hash || "",
122138
shortHash: shortHash || "",
123139
message: message || "",
124140
author: author || "",
125141
email: email || "",
126142
date: new Date(dateStr || ""),
143+
tags,
127144
};
128145
});
129146
},
@@ -157,6 +174,7 @@ export const createGitOperationsRouter = () => {
157174
const result = await withLockRetry(input.worktreePath, () =>
158175
git.commit(input.message)
159176
);
177+
invalidateGitStateCaches(input.worktreePath);
160178
return { success: true, hash: result.commit };
161179
});
162180
},
@@ -209,6 +227,7 @@ export const createGitOperationsRouter = () => {
209227
git.commit(input.message)
210228
);
211229

230+
invalidateGitStateCaches(input.worktreePath);
212231
return { success: true, hash: result.commit };
213232
});
214233
},
@@ -237,6 +256,7 @@ export const createGitOperationsRouter = () => {
237256
await withLockRetry(input.worktreePath, () => git.push());
238257
}
239258
await git.fetch();
259+
invalidateGitStateCaches(input.worktreePath);
240260
return { success: true };
241261
});
242262
}),
@@ -309,6 +329,7 @@ export const createGitOperationsRouter = () => {
309329
}
310330
throw error;
311331
}
332+
invalidateGitStateCaches(input.worktreePath);
312333
return { success: true };
313334
});
314335
}),
@@ -369,6 +390,7 @@ export const createGitOperationsRouter = () => {
369390
git.push(["--set-upstream", "origin", branch.trim()])
370391
);
371392
await git.fetch();
393+
invalidateGitStateCaches(input.worktreePath);
372394
return { success: true };
373395
}
374396
// Check for rebase conflicts
@@ -382,6 +404,7 @@ export const createGitOperationsRouter = () => {
382404
}
383405
await withLockRetry(input.worktreePath, () => git.push());
384406
await git.fetch();
407+
invalidateGitStateCaches(input.worktreePath);
385408
return { success: true };
386409
});
387410
}),
@@ -413,6 +436,7 @@ export const createGitOperationsRouter = () => {
413436
git.push(["--force-with-lease"])
414437
);
415438
await git.fetch();
439+
invalidateGitStateCaches(input.worktreePath);
416440
return { success: true };
417441
});
418442
}),
@@ -496,6 +520,7 @@ export const createGitOperationsRouter = () => {
496520
throw error;
497521
}
498522

523+
invalidateGitStateCaches(input.worktreePath);
499524
return { success: true };
500525
});
501526
}),
@@ -513,6 +538,7 @@ export const createGitOperationsRouter = () => {
513538
return withGitLock(input.worktreePath, async () => {
514539
const git = createGit(input.worktreePath);
515540
await git.rebase(["--abort"]);
541+
invalidateGitStateCaches(input.worktreePath);
516542
return { success: true };
517543
});
518544
}),
@@ -530,6 +556,7 @@ export const createGitOperationsRouter = () => {
530556
return withGitLock(input.worktreePath, async () => {
531557
const git = createGit(input.worktreePath);
532558
await git.merge(["--abort"]);
559+
invalidateGitStateCaches(input.worktreePath);
533560
return { success: true };
534561
});
535562
}),
@@ -586,6 +613,7 @@ export const createGitOperationsRouter = () => {
586613

587614
await shell.openExternal(url);
588615
await git.fetch();
616+
invalidateGitStateCaches(input.worktreePath);
589617

590618
return { success: true, url };
591619
});

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

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1181,10 +1181,10 @@ export const claudeRouter = router({
11811181
// Existing CLI config takes precedence over OAuth
11821182
const finalEnv = {
11831183
...claudeEnv,
1184-
// ...(claudeCodeToken &&
1185-
// !hasExistingApiConfig && {
1186-
// CLAUDE_CODE_OAUTH_TOKEN: claudeCodeToken,
1187-
// }),
1184+
...(claudeCodeToken &&
1185+
!hasExistingApiConfig && {
1186+
CLAUDE_CODE_OAUTH_TOKEN: claudeCodeToken,
1187+
}),
11881188
// Re-enable CLAUDE_CONFIG_DIR now that we properly map MCP configs
11891189
CLAUDE_CONFIG_DIR: isolatedConfigDir,
11901190
}
@@ -1536,9 +1536,7 @@ ${prompt}
15361536
Object.keys(mcpServersFiltered).length > 0 && {
15371537
mcpServers: mcpServersFiltered,
15381538
}),
1539-
env: {
1540-
...finalEnv,
1541-
},
1539+
env: finalEnv,
15421540
permissionMode:
15431541
input.mode === "plan"
15441542
? ("plan" as const)

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,10 @@ export type SelectedCommit = {
555555
} | null
556556
export const selectedCommitAtom = atom<SelectedCommit>(null)
557557

558+
// Active tab in diff sidebar (Changes/History)
559+
// Exposed as atom so external components (e.g. git activity badges) can switch tabs
560+
export const diffActiveTabAtom = atom<"changes" | "history">("changes")
561+
558562
// Pending PR message to send to chat
559563
// Set by ChatView when "Create PR" is clicked, consumed by ChatViewInner
560564
export const pendingPrMessageAtom = atom<{ message: string; subChatId: string } | null>(null)
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export const CLAUDE_MODELS = [
2-
{ id: "opus", name: "Opus 4.6" },
3-
{ id: "sonnet", name: "Sonnet 4.5" },
4-
{ id: "haiku", name: "Haiku 4.5" },
2+
{ id: "opus", name: "Opus", version: "4.6" },
3+
{ id: "sonnet", name: "Sonnet", version: "4.5" },
4+
{ id: "haiku", name: "Haiku", version: "4.5" },
55
]

src/renderer/features/agents/main/active-chat.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ import {
135135
QUESTIONS_SKIPPED_MESSAGE,
136136
selectedAgentChatIdAtom,
137137
selectedCommitAtom,
138+
diffActiveTabAtom,
138139
selectedDiffFilePathAtom,
139140
setLoading,
140141
subChatFilesAtom,
@@ -1157,8 +1158,8 @@ const DiffSidebarContent = memo(function DiffSidebarContent({
11571158
const [isChangesPanelCollapsed, setIsChangesPanelCollapsed] = useAtom(agentsChangesPanelCollapsedAtom)
11581159
const [isResizing, setIsResizing] = useState(false)
11591160

1160-
// Active tab state (Changes/History)
1161-
const [activeTab, setActiveTab] = useState<"changes" | "history">("changes")
1161+
// Active tab state (Changes/History) - atom so external components can switch tabs
1162+
const [activeTab, setActiveTab] = useAtom(diffActiveTabAtom)
11621163

11631164
// Register the reset function so handleCloseDiff can reset to "changes" tab before closing
11641165
// This prevents React 19 ref cleanup issues with HistoryView's ContextMenu components
@@ -1287,6 +1288,7 @@ const DiffSidebarContent = memo(function DiffSidebarContent({
12871288
)}>
12881289
<ChangesPanel
12891290
worktreePath={worktreePath}
1291+
activeTab={activeTab}
12901292
selectedFilePath={selectedFilePath}
12911293
onFileSelect={handleDiffFileSelect}
12921294
onFileOpenPinned={() => {}}
@@ -1401,6 +1403,7 @@ const DiffSidebarContent = memo(function DiffSidebarContent({
14011403
>
14021404
<ChangesPanel
14031405
worktreePath={worktreePath}
1406+
activeTab={activeTab}
14041407
selectedFilePath={selectedFilePath}
14051408
onFileSelect={handleDiffFileSelect}
14061409
onFileOpenPinned={() => {}}
@@ -5689,7 +5692,8 @@ Make sure to preserve all functionality from both branches when resolving confli
56895692
// Stable callbacks for DiffSidebarHeader to prevent re-renders
56905693
const handleRefreshGitStatus = useCallback(() => {
56915694
refetchGitStatus()
5692-
}, [refetchGitStatus])
5695+
scheduleDiffRefresh()
5696+
}, [refetchGitStatus, scheduleDiffRefresh])
56935697

56945698
const handleExpandAll = useCallback(() => {
56955699
diffViewRef.current?.expandAll()

src/renderer/features/agents/main/assistant-message-item.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
getMessageTextContent,
3636
} from "../ui/message-action-buttons"
3737
import { useFileOpen } from "../mentions"
38+
import { GitActivityBadges } from "../ui/git-activity-badges"
3839
import { MemoizedTextPart } from "./memoized-text-part"
3940

4041
// Exploring tools - these get grouped when 3+ consecutive
@@ -741,6 +742,9 @@ export const AssistantMessageItem = memo(function AssistantMessageItem({
741742
</div>
742743
)}
743744

745+
{/* Git activity badges - commit/PR pills */}
746+
{(!isStreaming || !isLastMessage) && <GitActivityBadges parts={messageParts} chatId={chatId} subChatId={subChatId} />}
747+
744748
{isDev && showMessageJson && (
745749
<div className="px-2 mt-2">
746750
<MessageJsonDisplay message={message} label="Assistant" />

src/renderer/features/agents/main/chat-input-area.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1412,7 +1412,10 @@ export const ChatInputArea = memo(function ChatInputArea({
14121412
{hasCustomClaudeConfig ? (
14131413
"Custom Model"
14141414
) : (
1415-
selectedModel?.name
1415+
<>
1416+
{selectedModel?.name}{" "}
1417+
<span className="text-muted-foreground">{selectedModel?.version}</span>
1418+
</>
14161419
)}
14171420
</span>
14181421
<ChevronDown className="h-3 w-3 shrink-0 opacity-50" />
@@ -1432,7 +1435,10 @@ export const ChatInputArea = memo(function ChatInputArea({
14321435
>
14331436
<div className="flex items-center gap-1.5">
14341437
<ClaudeCodeIcon className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
1435-
<span>{model.name}</span>
1438+
<span>
1439+
{model.name}{" "}
1440+
<span className="text-muted-foreground">{model.version}</span>
1441+
</span>
14361442
</div>
14371443
{isSelected && (
14381444
<CheckIcon className="h-3.5 w-3.5 shrink-0" />

src/renderer/features/agents/main/new-chat-form.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1813,7 +1813,7 @@ export function NewChatForm({
18131813
) : (
18141814
<>
18151815
{selectedModel?.name}{" "}
1816-
<span className="text-muted-foreground">4.5</span>
1816+
<span className="text-muted-foreground">{selectedModel?.version}</span>
18171817
</>
18181818
)}
18191819
</span>
@@ -1836,7 +1836,7 @@ export function NewChatForm({
18361836
<ClaudeCodeIcon className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
18371837
<span>
18381838
{model.name}{" "}
1839-
<span className="text-muted-foreground">4.5</span>
1839+
<span className="text-muted-foreground">{model.version}</span>
18401840
</span>
18411841
</div>
18421842
{isSelected && (

0 commit comments

Comments
 (0)