Skip to content

Commit b499906

Browse files
authored
feat: Reorganize settings and add permissions viewer (#1165)
1. Add getClaudePermissions tRPC endpoint to read allow/deny rules from ~/.claude/settings.json 2. Create PermissionsSettings component showing allowed and denied tool permissions as color-coded badges 3. Move "Keep awake" setting from Claude Code tab to General tab under new "Power" section 4. Reorganize Claude Code settings into "Extensions" and "Permissions" sections with border-separated headers 5. Apply consistent noBorder on last-in-section rows and update section header styling ![Screenshot 2026-03-10 at 11.24.24 AM.png](https://app.graphite.com/user-attachments/assets/e6454269-613a-4f4b-babc-ef543924fb4d.png)
1 parent 858e1e1 commit b499906

4 files changed

Lines changed: 224 additions & 67 deletions

File tree

apps/code/src/main/trpc/routers/os.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,33 @@ function downscaleImage(raw: Buffer, mimeType: string): DownscaledImage {
8888
};
8989
}
9090

91+
const claudeSettingsPath = path.join(os.homedir(), ".claude", "settings.json");
92+
9193
export const osRouter = router({
94+
getClaudePermissions: publicProcedure
95+
.output(
96+
z.object({
97+
allow: z.array(z.string()),
98+
deny: z.array(z.string()),
99+
}),
100+
)
101+
.query(async () => {
102+
try {
103+
const content = await fsPromises.readFile(claudeSettingsPath, "utf-8");
104+
const settings = JSON.parse(content);
105+
return {
106+
allow: Array.isArray(settings?.permissions?.allow)
107+
? settings.permissions.allow
108+
: [],
109+
deny: Array.isArray(settings?.permissions?.deny)
110+
? settings.permissions.deny
111+
: [],
112+
};
113+
} catch {
114+
return { allow: [], deny: [] };
115+
}
116+
}),
117+
92118
/**
93119
* Show directory picker dialog
94120
*/

apps/code/src/renderer/features/settings/components/sections/ClaudeCodeSettings.tsx

Lines changed: 50 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ import {
1212
Text,
1313
} from "@radix-ui/themes";
1414
import { Tooltip } from "@renderer/components/ui/Tooltip";
15-
import { trpcReact } from "@renderer/trpc";
1615
import { ANALYTICS_EVENTS } from "@shared/types/analytics";
1716
import { track } from "@utils/analytics";
18-
import { useCallback, useEffect, useState } from "react";
17+
import { useCallback, useState } from "react";
18+
import { PermissionsSettings } from "./PermissionsSettings";
1919

2020
function CopyableCommand({ command }: { command: string }) {
2121
const [copied, setCopied] = useState(false);
@@ -72,37 +72,11 @@ function SettingDescription({
7272
}
7373

7474
export function ClaudeCodeSettings() {
75-
const {
76-
allowBypassPermissions,
77-
setAllowBypassPermissions,
78-
preventSleepWhileRunning,
79-
setPreventSleepWhileRunning,
80-
} = useSettingsStore();
81-
82-
const { data: serverPreventSleep } = trpcReact.sleep.getEnabled.useQuery();
83-
const preventSleepMutation = trpcReact.sleep.setEnabled.useMutation();
84-
85-
useEffect(() => {
86-
if (serverPreventSleep !== undefined) {
87-
setPreventSleepWhileRunning(serverPreventSleep);
88-
}
89-
}, [serverPreventSleep, setPreventSleepWhileRunning]);
75+
const { allowBypassPermissions, setAllowBypassPermissions } =
76+
useSettingsStore();
9077

9178
const [showBypassWarning, setShowBypassWarning] = useState(false);
9279

93-
const handlePreventSleepChange = useCallback(
94-
(checked: boolean) => {
95-
track(ANALYTICS_EVENTS.SETTING_CHANGED, {
96-
setting_name: "prevent_sleep_while_running",
97-
new_value: checked,
98-
old_value: !checked,
99-
});
100-
setPreventSleepWhileRunning(checked);
101-
preventSleepMutation.mutate({ enabled: checked });
102-
},
103-
[setPreventSleepWhileRunning, preventSleepMutation],
104-
);
105-
10680
const handleBypassPermissionsChange = useCallback(
10781
(checked: boolean) => {
10882
if (checked) {
@@ -131,38 +105,10 @@ export function ClaudeCodeSettings() {
131105

132106
return (
133107
<Flex direction="column">
134-
<SettingRow
135-
label="Keep awake while agents work"
136-
description="Prevent your computer from sleeping while the agent is running a task"
137-
>
138-
<Switch
139-
checked={preventSleepWhileRunning}
140-
onCheckedChange={handlePreventSleepChange}
141-
size="1"
142-
/>
143-
</SettingRow>
144-
145-
<SettingRow
146-
label="Enable Bypass Permissions mode"
147-
description="Enables 'Bypass Permissions' mode in the execution mode selector. When active, PostHog Code will not ask for approval before running potentially dangerous commands."
148-
>
149-
<Switch
150-
checked={allowBypassPermissions}
151-
onCheckedChange={handleBypassPermissionsChange}
152-
size="1"
153-
color="red"
154-
/>
155-
</SettingRow>
156-
{allowBypassPermissions && (
157-
<Callout.Root size="1" color="red" mb="3">
158-
<Callout.Icon>
159-
<Warning weight="fill" />
160-
</Callout.Icon>
161-
<Callout.Text>
162-
Bypass Permissions mode is enabled. Use with extreme caution.
163-
</Callout.Text>
164-
</Callout.Root>
165-
)}
108+
{/* Extensions */}
109+
<Text size="2" weight="medium" className="mt-1 mb-2">
110+
Extensions
111+
</Text>
166112

167113
<SettingRow
168114
label="MCP servers"
@@ -213,6 +159,48 @@ export function ClaudeCodeSettings() {
213159
<CopyableCommand command="claude /hooks" />
214160
</SettingRow>
215161

162+
{/* Permissions */}
163+
<Text
164+
size="2"
165+
weight="medium"
166+
className="mb-2 block border-gray-6 border-t pt-4"
167+
>
168+
Permissions
169+
</Text>
170+
171+
<SettingRow
172+
label="Permission rules"
173+
description="Tool permissions from your Claude settings. Allowed tools run without prompting. Denied tools are always blocked."
174+
>
175+
<CopyableCommand command="claude config" />
176+
</SettingRow>
177+
178+
<PermissionsSettings />
179+
180+
<SettingRow
181+
label="Bypass Permissions mode"
182+
description="Skips all permission rules. PostHog Code will run every tool without asking for approval."
183+
noBorder
184+
>
185+
<Switch
186+
checked={allowBypassPermissions}
187+
onCheckedChange={handleBypassPermissionsChange}
188+
size="1"
189+
color="red"
190+
/>
191+
</SettingRow>
192+
{allowBypassPermissions && (
193+
<Callout.Root size="1" color="red" mb="3">
194+
<Callout.Icon>
195+
<Warning weight="fill" />
196+
</Callout.Icon>
197+
<Callout.Text>
198+
Bypass Permissions mode is enabled. All permission rules are
199+
ignored.
200+
</Callout.Text>
201+
</Callout.Root>
202+
)}
203+
216204
<AlertDialog.Root
217205
open={showBypassWarning}
218206
onOpenChange={setShowBypassWarning}

apps/code/src/renderer/features/settings/components/sections/GeneralSettings.tsx

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
Text,
1717
TextField,
1818
} from "@radix-ui/themes";
19+
import { trpcReact } from "@renderer/trpc";
1920
import { getCloudUrlFromRegion } from "@shared/constants/oauth";
2021
import { ANALYTICS_EVENTS } from "@shared/types/analytics";
2122
import { useSettingsStore as useTerminalSettingsStore } from "@stores/settingsStore";
@@ -60,6 +61,31 @@ export function GeneralSettings() {
6061
(state) => state.setTerminalFontFamily,
6162
);
6263

64+
// Power state
65+
const { preventSleepWhileRunning, setPreventSleepWhileRunning } =
66+
useSettingsStore();
67+
const { data: serverPreventSleep } = trpcReact.sleep.getEnabled.useQuery();
68+
const preventSleepMutation = trpcReact.sleep.setEnabled.useMutation();
69+
70+
useEffect(() => {
71+
if (serverPreventSleep !== undefined) {
72+
setPreventSleepWhileRunning(serverPreventSleep);
73+
}
74+
}, [serverPreventSleep, setPreventSleepWhileRunning]);
75+
76+
const handlePreventSleepChange = useCallback(
77+
(checked: boolean) => {
78+
track(ANALYTICS_EVENTS.SETTING_CHANGED, {
79+
setting_name: "prevent_sleep_while_running",
80+
new_value: checked,
81+
old_value: !checked,
82+
});
83+
setPreventSleepWhileRunning(checked);
84+
preventSleepMutation.mutate({ enabled: checked });
85+
},
86+
[setPreventSleepWhileRunning, preventSleepMutation],
87+
);
88+
6389
// Chat state
6490
const {
6591
desktopNotifications,
@@ -381,7 +407,11 @@ export function GeneralSettings() {
381407
)}
382408

383409
{/* Notifications */}
384-
<Text size="2" weight="medium" className="mt-4 mb-2">
410+
<Text
411+
size="2"
412+
weight="medium"
413+
className="mb-2 block border-gray-6 border-t pt-4"
414+
>
385415
Notifications
386416
</Text>
387417

@@ -430,6 +460,7 @@ export function GeneralSettings() {
430460
<SettingRow
431461
label="Sound effect"
432462
description="Play a sound when the agent finishes a task or needs your input"
463+
noBorder={completionSound === "none"}
433464
>
434465
<Flex align="center" gap="2">
435466
<Select.Root
@@ -457,7 +488,7 @@ export function GeneralSettings() {
457488
</SettingRow>
458489

459490
{completionSound !== "none" && (
460-
<SettingRow label="Sound volume">
491+
<SettingRow label="Sound volume" noBorder>
461492
<Flex align="center" gap="3">
462493
<Slider
463494
value={[completionVolume]}
@@ -476,7 +507,11 @@ export function GeneralSettings() {
476507
)}
477508

478509
{/* Input */}
479-
<Text size="2" weight="medium" className="mt-4 mb-2">
510+
<Text
511+
size="2"
512+
weight="medium"
513+
className="mb-2 block border-gray-6 border-t pt-4"
514+
>
480515
Input
481516
</Text>
482517

@@ -502,6 +537,7 @@ export function GeneralSettings() {
502537
<SettingRow
503538
label="Auto-convert long text"
504539
description="Automatically convert pasted text over 500 characters into an attachment"
540+
noBorder
505541
>
506542
<Switch
507543
checked={autoConvertLongText}
@@ -511,7 +547,11 @@ export function GeneralSettings() {
511547
</SettingRow>
512548

513549
{/* Editor */}
514-
<Text size="2" weight="medium" className="mt-4 mb-2">
550+
<Text
551+
size="2"
552+
weight="medium"
553+
className="mb-2 block border-gray-6 border-t pt-4"
554+
>
515555
Editor
516556
</Text>
517557

@@ -537,8 +577,33 @@ export function GeneralSettings() {
537577
</Select.Root>
538578
</SettingRow>
539579

580+
{/* Power */}
581+
<Text
582+
size="2"
583+
weight="medium"
584+
className="mb-2 block border-gray-6 border-t pt-4"
585+
>
586+
Power
587+
</Text>
588+
589+
<SettingRow
590+
label="Keep awake while agents work"
591+
description="Prevent your computer from sleeping while the agent is running a task"
592+
noBorder
593+
>
594+
<Switch
595+
checked={preventSleepWhileRunning}
596+
onCheckedChange={handlePreventSleepChange}
597+
size="1"
598+
/>
599+
</SettingRow>
600+
540601
{/* Fun */}
541-
<Text size="2" weight="medium" className="mt-4 mb-2">
602+
<Text
603+
size="2"
604+
weight="medium"
605+
className="mb-2 block border-gray-6 border-t pt-4"
606+
>
542607
Fun
543608
</Text>
544609

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { Box, Flex, Text } from "@radix-ui/themes";
2+
import { trpcReact } from "@renderer/trpc";
3+
4+
function PermissionBadge({
5+
permission,
6+
color,
7+
}: {
8+
permission: string;
9+
color: "green" | "red";
10+
}) {
11+
const bgClass = color === "green" ? "bg-green-500/20" : "bg-red-500/20";
12+
const textClass = color === "green" ? "text-green-400" : "text-red-400";
13+
const borderClass =
14+
color === "green" ? "border-green-500/30" : "border-red-500/30";
15+
16+
return (
17+
<span
18+
className={`rounded border px-1.5 py-0.5 font-mono text-[11px] leading-tight ${bgClass} ${textClass} ${borderClass}`}
19+
>
20+
{permission}
21+
</span>
22+
);
23+
}
24+
25+
function PermissionList({
26+
title,
27+
permissions,
28+
color,
29+
emptyMessage,
30+
}: {
31+
title: string;
32+
permissions: string[];
33+
color: "green" | "red";
34+
emptyMessage: string;
35+
}) {
36+
return (
37+
<Box className="rounded-lg border border-gray-6 bg-gray-2 p-3">
38+
<Text size="1" weight="medium" className="mb-2 block">
39+
{title}
40+
</Text>
41+
<Box className="min-h-[40px] rounded border border-gray-5 bg-gray-3 p-2.5">
42+
{permissions.length > 0 ? (
43+
<Flex wrap="wrap" gap="2">
44+
{permissions.map((perm) => (
45+
<PermissionBadge key={perm} permission={perm} color={color} />
46+
))}
47+
</Flex>
48+
) : (
49+
<Text size="1" color="gray">
50+
{emptyMessage}
51+
</Text>
52+
)}
53+
</Box>
54+
</Box>
55+
);
56+
}
57+
58+
export function PermissionsSettings() {
59+
const { data } = trpcReact.os.getClaudePermissions.useQuery();
60+
61+
return (
62+
<Flex direction="column" gap="3" mb="2">
63+
<PermissionList
64+
title="Allowed"
65+
permissions={data?.allow ?? []}
66+
color="green"
67+
emptyMessage="No allowed permissions configured"
68+
/>
69+
70+
<PermissionList
71+
title="Denied"
72+
permissions={data?.deny ?? []}
73+
color="red"
74+
emptyMessage="No denied permissions configured"
75+
/>
76+
</Flex>
77+
);
78+
}

0 commit comments

Comments
 (0)