Skip to content

Commit ec9329c

Browse files
committed
Release v0.0.42
## What's New ### Features - **Drag & Drop File Mentions** — Drag files to chat with hidden content support - **Custom Hotkeys in Tooltips** — Tooltips now show your custom keyboard shortcuts - **Sidebar Mutual Exclusion** — Sidebars auto-restore when switching between them ### Improvements & Fixes - **MCP OAuth Fix** — Fixed authentication for servers with client allowlists - **Relative Paths** — Show relative paths instead of absolute in UI - **Slash Commands** — Fixed multi-line arguments and auto-send behavior - **Cmd+R Disabled** — Prevent accidental page refresh - **Pasted Text Fix** — Sanitize pasted text preview to fix mention parsing
1 parent 8fd2c5c commit ec9329c

30 files changed

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

src/main/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -670,7 +670,8 @@ if (gotTheLock) {
670670
{
671671
label: "View",
672672
submenu: [
673-
{ role: "reload" },
673+
// Cmd+R is disabled to prevent accidental page refresh
674+
// Use Cmd+Shift+R (Force Reload) for intentional reloads
674675
{ role: "forceReload" },
675676
// Only show DevTools in dev mode or when unlocked via hidden feature
676677
...(showDevTools ? [{ role: "toggleDevTools" as const }] : []),

src/main/lib/trpc/routers/agent-utils.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,8 @@ export async function loadAgent(
155155
*/
156156
export async function scanAgentsDirectory(
157157
dir: string,
158-
source: "user" | "project"
158+
source: "user" | "project",
159+
basePath?: string // For project agents, the cwd to make paths relative to
159160
): Promise<FileAgent[]> {
160161
const agents: FileAgent[] = []
161162

@@ -182,6 +183,18 @@ export async function scanAgentsDirectory(
182183
const parsed = parseAgentMd(content, entry.name)
183184

184185
if (parsed.description && parsed.prompt) {
186+
// For project agents, show relative path; for user agents, show ~/.claude/... path
187+
let displayPath: string
188+
if (source === "project" && basePath) {
189+
displayPath = path.relative(basePath, agentPath)
190+
} else {
191+
// For user agents, show ~/.claude/agents/... format
192+
const homeDir = os.homedir()
193+
displayPath = agentPath.startsWith(homeDir)
194+
? "~" + agentPath.slice(homeDir.length)
195+
: agentPath
196+
}
197+
185198
agents.push({
186199
name: parsed.name || entry.name.replace(".md", ""),
187200
description: parsed.description,
@@ -190,7 +203,7 @@ export async function scanAgentsDirectory(
190203
disallowedTools: parsed.disallowedTools,
191204
model: parsed.model,
192205
source,
193-
path: agentPath,
206+
path: displayPath,
194207
})
195208
}
196209
} catch (err) {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const listAgentsProcedure = publicProcedure
2727
let projectAgentsPromise = Promise.resolve<FileAgent[]>([])
2828
if (input?.cwd) {
2929
const projectAgentsDir = path.join(input.cwd, ".claude", "agents")
30-
projectAgentsPromise = scanAgentsDirectory(projectAgentsDir, "project")
30+
projectAgentsPromise = scanAgentsDirectory(projectAgentsDir, "project", input.cwd)
3131
}
3232

3333
const [userAgents, projectAgents] = await Promise.all([

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,12 @@ export const chatsRouter = router({
264264
base64Data: z.string().optional(),
265265
}),
266266
}),
267+
// Hidden file content - sent to agent but not displayed in UI
268+
z.object({
269+
type: z.literal("file-content"),
270+
filePath: z.string(),
271+
content: z.string(),
272+
}),
267273
]),
268274
)
269275
.optional(),

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ import { buildAgentsOption } from "./agent-utils"
2727
/**
2828
* Parse @[agent:name], @[skill:name], and @[tool:name] mentions from prompt text
2929
* Returns the cleaned prompt and lists of mentioned agents/skills/tools
30+
*
31+
* File mention formats:
32+
* - @[file:local:relative/path] - file inside project (relative path)
33+
* - @[file:external:/absolute/path] - file outside project (absolute path)
34+
* - @[file:owner/repo:path] - legacy web format (repo:path)
35+
* - @[folder:local:path] or @[folder:external:path] - folder mentions
3036
*/
3137
function parseMentions(prompt: string): {
3238
cleanedPrompt: string
@@ -79,6 +85,15 @@ function parseMentions(prompt: string): {
7985
.replace(/@\[tool:[^\]]+\]/g, "")
8086
.trim()
8187

88+
// Transform file mentions to readable paths for the agent
89+
// @[file:local:path] -> path (relative to project)
90+
// @[file:external:/abs/path] -> /abs/path (absolute)
91+
cleanedPrompt = cleanedPrompt
92+
.replace(/@\[file:local:([^\]]+)\]/g, "$1")
93+
.replace(/@\[file:external:([^\]]+)\]/g, "$1")
94+
.replace(/@\[folder:local:([^\]]+)\]/g, "$1")
95+
.replace(/@\[folder:external:([^\]]+)\]/g, "$1")
96+
8297
// Add tool usage hints if tools were mentioned
8398
// Tool names are already validated to contain only safe characters
8499
if (toolMentions.length > 0) {

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ function parseSkillMd(content: string): { name?: string; description?: string }
3434
async function scanSkillsDirectory(
3535
dir: string,
3636
source: "user" | "project",
37+
basePath?: string, // For project skills, the cwd to make paths relative to
3738
): Promise<FileSkill[]> {
3839
const skills: FileSkill[] = []
3940

@@ -63,11 +64,23 @@ async function scanSkillsDirectory(
6364
const content = await fs.readFile(skillMdPath, "utf-8")
6465
const parsed = parseSkillMd(content)
6566

67+
// For project skills, show relative path; for user skills, show ~/.claude/... path
68+
let displayPath: string
69+
if (source === "project" && basePath) {
70+
displayPath = path.relative(basePath, skillMdPath)
71+
} else {
72+
// For user skills, show ~/.claude/skills/... format
73+
const homeDir = os.homedir()
74+
displayPath = skillMdPath.startsWith(homeDir)
75+
? "~" + skillMdPath.slice(homeDir.length)
76+
: skillMdPath
77+
}
78+
6679
skills.push({
6780
name: parsed.name || entry.name,
6881
description: parsed.description || "",
6982
source,
70-
path: skillMdPath,
83+
path: displayPath,
7184
})
7285
} catch (err) {
7386
// Skill directory doesn't have SKILL.md or read failed - skip it
@@ -96,7 +109,7 @@ const listSkillsProcedure = publicProcedure
96109
let projectSkillsPromise = Promise.resolve<FileSkill[]>([])
97110
if (input?.cwd) {
98111
const projectSkillsDir = path.join(input.cwd, ".claude", "skills")
99-
projectSkillsPromise = scanSkillsDirectory(projectSkillsDir, "project")
112+
projectSkillsPromise = scanSkillsDirectory(projectSkillsDir, "project", input.cwd)
100113
}
101114

102115
// Scan both directories in parallel

src/main/windows/main.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,16 @@ export function createMainWindow(): BrowserWindow {
428428
window.webContents.send("window:focus-change", false)
429429
})
430430

431+
// Disable Cmd+R / Ctrl+R to prevent accidental page refresh
432+
// Users can still use Cmd+Shift+R / Ctrl+Shift+R for intentional reloads
433+
window.webContents.on("before-input-event", (event, input) => {
434+
const isMac = process.platform === "darwin"
435+
const modifierKey = isMac ? input.meta : input.control
436+
if (modifierKey && input.key.toLowerCase() === "r" && !input.shift) {
437+
event.preventDefault()
438+
}
439+
})
440+
431441
// Handle external links
432442
window.webContents.setWindowOpenHandler(({ url }) => {
433443
shell.openExternal(url)

src/preload/index.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { contextBridge, ipcRenderer } from "electron"
1+
import { contextBridge, ipcRenderer, webUtils } from "electron"
22
import { exposeElectronTRPC } from "trpc-electron/main"
33

44
// Only initialize Sentry in production to avoid IPC errors in dev mode
@@ -11,6 +11,11 @@ if (process.env.NODE_ENV === "production") {
1111
// Expose tRPC IPC bridge for type-safe communication
1212
exposeElectronTRPC()
1313

14+
// Expose webUtils for file path access in drag and drop
15+
contextBridge.exposeInMainWorld("webUtils", {
16+
getPathForFile: (file: File) => webUtils.getPathForFile(file),
17+
})
18+
1419
// Expose analytics force flag for testing
1520
if (process.env.FORCE_ANALYTICS === "true") {
1621
contextBridge.exposeInMainWorld("__FORCE_ANALYTICS__", true)
@@ -256,5 +261,8 @@ export interface DesktopApi {
256261
declare global {
257262
interface Window {
258263
desktopApi: DesktopApi
264+
webUtils: {
265+
getPathForFile: (file: File) => string
266+
}
259267
}
260268
}

src/renderer/features/agents/components/agent-send-button.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
TooltipContent,
1313
TooltipTrigger,
1414
} from "../../../components/ui/tooltip"
15+
import { useResolvedHotkeyDisplayWithAlt } from "../../../lib/hotkeys"
1516

1617
interface AgentSendButtonProps {
1718
/** Whether the system is currently streaming */
@@ -48,6 +49,9 @@ export function AgentSendButton({
4849
isPlanMode = false,
4950
hasContent = false,
5051
}: AgentSendButtonProps) {
52+
// Resolved hotkeys for stop-generation tooltip
53+
const stopHotkey = useResolvedHotkeyDisplayWithAlt("stop-generation")
54+
5155
// Note: Enter shortcut is now handled by input components directly
5256

5357
// When streaming AND user has typed content, show arrow to add to queue
@@ -88,9 +92,13 @@ export function AgentSendButton({
8892
return (
8993
<span className="flex items-center gap-1">
9094
Stop
91-
<Kbd className="ms-0.5">Esc</Kbd>
92-
<span className="text-muted-foreground/60">or</span>
93-
<Kbd className="-me-1">Ctrl C</Kbd>
95+
{stopHotkey.primary && <Kbd className="ms-0.5">{stopHotkey.primary}</Kbd>}
96+
{stopHotkey.alt && (
97+
<>
98+
<span className="text-muted-foreground/60">or</span>
99+
<Kbd className="-me-1">{stopHotkey.alt}</Kbd>
100+
</>
101+
)}
94102
</span>
95103
)
96104
if (isStreaming && hasContent)

0 commit comments

Comments
 (0)