From 85fb0432ae12ed2b6ab99c8bc8d6181e233dfd77 Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Sun, 17 May 2026 15:53:30 -0700 Subject: [PATCH 1/5] Misc fixes --- apps/code/src/renderer/utils/toast.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/apps/code/src/renderer/utils/toast.tsx b/apps/code/src/renderer/utils/toast.tsx index ccd4032ca..affcd22a2 100644 --- a/apps/code/src/renderer/utils/toast.tsx +++ b/apps/code/src/renderer/utils/toast.tsx @@ -1,4 +1,10 @@ -import { CheckIcon, InfoIcon, WarningIcon, XIcon } from "@phosphor-icons/react"; +import { + CheckIcon, + InfoIcon, + WarningCircleIcon, + WarningIcon, + XIcon, +} from "@phosphor-icons/react"; import { Card, Flex, IconButton, Spinner, Text } from "@radix-ui/themes"; import type { ReactNode } from "react"; import { toast as sonnerToast } from "sonner"; @@ -26,7 +32,9 @@ function ToastComponent(props: ToastProps) { case "success": return ; case "error": - return ; + return ( + + ); case "info": return ; case "warning": From e07a6a6cec01bd7faf9d061f242f07c1948e4390 Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Sun, 17 May 2026 18:11:32 -0700 Subject: [PATCH 2/5] fix garbled initials in settings avatar --- .../features/auth/utils/userInitials.ts | 27 +++++++++++++++++++ .../settings/components/SettingsDialog.tsx | 9 +++---- .../components/sections/AccountSettings.tsx | 6 ++--- 3 files changed, 32 insertions(+), 10 deletions(-) create mode 100644 apps/code/src/renderer/features/auth/utils/userInitials.ts diff --git a/apps/code/src/renderer/features/auth/utils/userInitials.ts b/apps/code/src/renderer/features/auth/utils/userInitials.ts new file mode 100644 index 000000000..6864e8ae1 --- /dev/null +++ b/apps/code/src/renderer/features/auth/utils/userInitials.ts @@ -0,0 +1,27 @@ +interface UserLike { + first_name?: string | null; + last_name?: string | null; + email?: string | null; +} + +function firstLetter(value: string | null | undefined): string | null { + if (!value) return null; + const match = value.match(/\p{L}/u); + return match ? match[0] : null; +} + +export function getUserInitials(user: UserLike | null | undefined): string { + const first = firstLetter(user?.first_name); + const last = firstLetter(user?.last_name); + if (first && last) { + return `${first}${last}`.toUpperCase(); + } + if (first) { + return first.toUpperCase(); + } + const emailLetters = user?.email?.match(/\p{L}/gu)?.slice(0, 2).join(""); + if (emailLetters) { + return emailLetters.toUpperCase(); + } + return "U"; +} diff --git a/apps/code/src/renderer/features/settings/components/SettingsDialog.tsx b/apps/code/src/renderer/features/settings/components/SettingsDialog.tsx index c2d76c1ab..46ff6a1fb 100644 --- a/apps/code/src/renderer/features/settings/components/SettingsDialog.tsx +++ b/apps/code/src/renderer/features/settings/components/SettingsDialog.tsx @@ -4,6 +4,7 @@ import { useAuthStateValue, useCurrentUser, } from "@features/auth/hooks/authQueries"; +import { getUserInitials } from "@features/auth/utils/userInitials"; import { type SettingsCategory, useSettingsDialogStore, @@ -162,11 +163,7 @@ export function SettingsDialog() { const ActiveComponent = CATEGORY_COMPONENTS[activeCategory]; - const initials = user - ? user.first_name && user.last_name - ? `${user.first_name[0]}${user.last_name[0]}`.toUpperCase() - : (user.email?.substring(0, 2).toUpperCase() ?? "U") - : null; + const initials = getUserInitials(user); return (
- {isAuthenticated && user && initials && ( + {isAuthenticated && user && ( From e663f36f393e09913df57d03f3c2a5d6f57f73af Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Sun, 17 May 2026 18:12:22 -0700 Subject: [PATCH 3/5] always render markdown files Generated-By: PostHog Code Task-Id: 5f514df8-a02d-42c8-bb24-e972875c458d --- .../components/CodeEditorPanel.tsx | 42 ++++--------------- .../code-editor/stores/markdownViewerStore.ts | 26 ------------ 2 files changed, 9 insertions(+), 59 deletions(-) delete mode 100644 apps/code/src/renderer/features/code-editor/stores/markdownViewerStore.ts diff --git a/apps/code/src/renderer/features/code-editor/components/CodeEditorPanel.tsx b/apps/code/src/renderer/features/code-editor/components/CodeEditorPanel.tsx index dccd768e9..0cdb2e6df 100644 --- a/apps/code/src/renderer/features/code-editor/components/CodeEditorPanel.tsx +++ b/apps/code/src/renderer/features/code-editor/components/CodeEditorPanel.tsx @@ -5,14 +5,13 @@ import { CodeMirrorEditor } from "@features/code-editor/components/CodeMirrorEdi import { EnrichmentPopover } from "@features/code-editor/components/EnrichmentPopover"; import { useCloudFileContent } from "@features/code-editor/hooks/useCloudFileContent"; import { useFileEnrichment } from "@features/code-editor/hooks/useFileEnrichment"; -import { useMarkdownViewerStore } from "@features/code-editor/stores/markdownViewerStore"; import { isMarkdownFile } from "@features/code-editor/utils/markdownUtils"; import { getRelativePath } from "@features/code-editor/utils/pathUtils"; import { usePanelLayoutStore } from "@features/panels"; import { useFileTreeStore } from "@features/right-sidebar/stores/fileTreeStore"; import { useCwd } from "@features/sidebar/hooks/useCwd"; import { useIsWorkspaceCloudRun } from "@features/workspace/hooks/useWorkspace"; -import { Check, Code, Copy, Eye } from "@phosphor-icons/react"; +import { Check, Copy } from "@phosphor-icons/react"; import { Box, Flex, IconButton, Text } from "@radix-ui/themes"; import { trpcClient, useTRPC } from "@renderer/trpc/client"; import { getImageMimeType, isImageFile } from "@shared/constants/image"; @@ -76,10 +75,6 @@ export function CodeEditorPanel({ const filePath = getRelativePath(absolutePath, repoPath); const isImage = isImageFile(absolutePath); const isMarkdown = isMarkdownFile(absolutePath); - const preferRendered = useMarkdownViewerStore((s) => s.preferRendered); - const togglePreferRendered = useMarkdownViewerStore( - (s) => s.togglePreferRendered, - ); const openFileInSplit = usePanelLayoutStore((s) => s.openFileInSplit); const expandToFile = useFileTreeStore((s) => s.expandToFile); const [copied, setCopied] = useState(false); @@ -272,36 +267,17 @@ export function CodeEditorPanel({ {copied ? : } - - - {preferRendered ? : } - - - {preferRendered ? ( - - - {fileContent} - - - ) : ( - - )} + + + {fileContent} + + ); diff --git a/apps/code/src/renderer/features/code-editor/stores/markdownViewerStore.ts b/apps/code/src/renderer/features/code-editor/stores/markdownViewerStore.ts deleted file mode 100644 index c3ef75831..000000000 --- a/apps/code/src/renderer/features/code-editor/stores/markdownViewerStore.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { create } from "zustand"; -import { persist } from "zustand/middleware"; - -interface MarkdownViewerStoreState { - preferRendered: boolean; -} - -interface MarkdownViewerStoreActions { - togglePreferRendered: () => void; -} - -type MarkdownViewerStore = MarkdownViewerStoreState & - MarkdownViewerStoreActions; - -export const useMarkdownViewerStore = create()( - persist( - (set) => ({ - preferRendered: true, - togglePreferRendered: () => - set((s) => ({ preferRendered: !s.preferRendered })), - }), - { - name: "markdown-viewer-storage", - }, - ), -); From 2125e442cc7b6c9b38514552f0a97852c22d83ff Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Sun, 17 May 2026 18:22:35 -0700 Subject: [PATCH 4/5] strip code fences from bash tool output Generated-By: PostHog Code Task-Id: 8b514353-44fc-4561-8075-bca46f95d645 --- .../sessions/components/session-update/ExecuteToolView.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/code/src/renderer/features/sessions/components/session-update/ExecuteToolView.tsx b/apps/code/src/renderer/features/sessions/components/session-update/ExecuteToolView.tsx index a0a80930a..b99eceeae 100644 --- a/apps/code/src/renderer/features/sessions/components/session-update/ExecuteToolView.tsx +++ b/apps/code/src/renderer/features/sessions/components/session-update/ExecuteToolView.tsx @@ -7,6 +7,7 @@ import { ExpandedContentBox, getContentText, StatusIndicators, + stripCodeFences, ToolTitle, type ToolViewProps, truncateText, @@ -40,7 +41,10 @@ export function ExecuteToolView({ const description = executeInput?.description ?? (command ? undefined : title); - const output = (getContentText(content) ?? "").replace(ANSI_REGEX, ""); + const output = stripCodeFences(getContentText(content) ?? "").replace( + ANSI_REGEX, + "", + ); const hasOutput = output.trim().length > 0; const isExpandable = hasOutput; From 1b824aeea59ebfd2dcefbd9beced7e586b9a7efd Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Sun, 17 May 2026 23:10:29 -0700 Subject: [PATCH 5/5] address review feedback on initials and toast --- .../features/auth/utils/userInitials.test.ts | 89 +++++++++++++++++++ .../features/auth/utils/userInitials.ts | 6 +- apps/code/src/renderer/utils/toast.tsx | 2 +- 3 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 apps/code/src/renderer/features/auth/utils/userInitials.test.ts diff --git a/apps/code/src/renderer/features/auth/utils/userInitials.test.ts b/apps/code/src/renderer/features/auth/utils/userInitials.test.ts new file mode 100644 index 000000000..1f2f387fd --- /dev/null +++ b/apps/code/src/renderer/features/auth/utils/userInitials.test.ts @@ -0,0 +1,89 @@ +import { describe, expect, it } from "vitest"; +import { getUserInitials } from "./userInitials"; + +describe("getUserInitials", () => { + it("returns uppercased first+last initials when both are set", () => { + expect(getUserInitials({ first_name: "Charles", last_name: "Vien" })).toBe( + "CV", + ); + }); + + it("uppercases lowercase names", () => { + expect(getUserInitials({ first_name: "alice", last_name: "smith" })).toBe( + "AS", + ); + }); + + it("returns the first initial when only first_name is set", () => { + expect(getUserInitials({ first_name: "Charles" })).toBe("C"); + }); + + it("returns the last initial when only last_name is set", () => { + expect(getUserInitials({ last_name: "Vien" })).toBe("V"); + }); + + it("falls back to the first two letters of the email local part", () => { + expect(getUserInitials({ email: "charles.v@posthog.com" })).toBe("CH"); + }); + + it("never pulls letters from the email domain", () => { + expect(getUserInitials({ email: "1234@example.com" })).toBe("U"); + }); + + it("skips non-letter chars when extracting from names", () => { + expect(getUserInitials({ first_name: " 123Alice" })).toBe("A"); + }); + + it("skips non-letter chars when extracting from email local part", () => { + expect(getUserInitials({ email: "1.2_charles@posthog.com" })).toBe("CH"); + }); + + it("handles astral-plane characters without producing lone surrogates", () => { + // U+20BB7 ("𠮷") is encoded as a UTF-16 surrogate pair. The old + // implementation used string[0], which returned only the high surrogate + // and rendered as a garbled tofu char. + expect(getUserInitials({ first_name: "𠮷田", last_name: "Smith" })).toBe( + "𠮷S", + ); + }); + + it("handles accented characters", () => { + expect(getUserInitials({ first_name: "Émile", last_name: "Über" })).toBe( + "ÉÜ", + ); + }); + + it("returns 'U' for a null user", () => { + expect(getUserInitials(null)).toBe("U"); + }); + + it("returns 'U' for an undefined user", () => { + expect(getUserInitials(undefined)).toBe("U"); + }); + + it("returns 'U' when every field is an empty string", () => { + expect(getUserInitials({ first_name: "", last_name: "", email: "" })).toBe( + "U", + ); + }); + + it("returns 'U' when names have no letters and there is no email", () => { + expect(getUserInitials({ first_name: "123" })).toBe("U"); + }); + + it("returns 'U' when names have no letters and the email local part has no letters", () => { + expect( + getUserInitials({ first_name: "123", email: "456@example.com" }), + ).toBe("U"); + }); + + it("ignores null name fields and uses the email fallback", () => { + expect( + getUserInitials({ + first_name: null, + last_name: null, + email: "charles.v@posthog.com", + }), + ).toBe("CH"); + }); +}); diff --git a/apps/code/src/renderer/features/auth/utils/userInitials.ts b/apps/code/src/renderer/features/auth/utils/userInitials.ts index 6864e8ae1..c51b65890 100644 --- a/apps/code/src/renderer/features/auth/utils/userInitials.ts +++ b/apps/code/src/renderer/features/auth/utils/userInitials.ts @@ -19,7 +19,11 @@ export function getUserInitials(user: UserLike | null | undefined): string { if (first) { return first.toUpperCase(); } - const emailLetters = user?.email?.match(/\p{L}/gu)?.slice(0, 2).join(""); + if (last) { + return last.toUpperCase(); + } + const emailLocal = user?.email?.split("@")[0]; + const emailLetters = emailLocal?.match(/\p{L}/gu)?.slice(0, 2).join(""); if (emailLetters) { return emailLetters.toUpperCase(); } diff --git a/apps/code/src/renderer/utils/toast.tsx b/apps/code/src/renderer/utils/toast.tsx index affcd22a2..e1d610b58 100644 --- a/apps/code/src/renderer/utils/toast.tsx +++ b/apps/code/src/renderer/utils/toast.tsx @@ -33,7 +33,7 @@ function ToastComponent(props: ToastProps) { return ; case "error": return ( - + ); case "info": return ;