From 3b32077a6b50111dd08cdb6333a8e84d09713639 Mon Sep 17 00:00:00 2001 From: cleo-pleurodon Date: Wed, 13 May 2026 14:22:49 -0400 Subject: [PATCH 1/2] chat banner entry point --- .../message-editor/components/PromptInput.tsx | 3 ++ .../message-editor/tiptap/useTiptapEditor.ts | 5 ++ .../sessions/components/SessionView.tsx | 25 ++++++++++ .../components/TryInPostHogWorkBanner.tsx | 49 +++++++++++++++++++ .../task-detail/components/TaskInput.tsx | 25 ++++++++++ 5 files changed, 107 insertions(+) create mode 100644 apps/code/src/renderer/features/sessions/components/TryInPostHogWorkBanner.tsx diff --git a/apps/code/src/renderer/features/message-editor/components/PromptInput.tsx b/apps/code/src/renderer/features/message-editor/components/PromptInput.tsx index 9ebd58675..b6d9c842c 100644 --- a/apps/code/src/renderer/features/message-editor/components/PromptInput.tsx +++ b/apps/code/src/renderer/features/message-editor/components/PromptInput.tsx @@ -52,6 +52,7 @@ export interface PromptInputProps { onCancel?: () => void; onAttachFiles?: (files: File[]) => void; onEmptyChange?: (isEmpty: boolean) => void; + onTextChange?: (text: string) => void; onFocus?: () => void; onBlur?: () => void; // manual submit override (for flows like new-task that submit outside the editor hook) @@ -90,6 +91,7 @@ export const PromptInput = forwardRef( onCancel, onAttachFiles, onEmptyChange, + onTextChange, onFocus, onBlur, onSubmitClick, @@ -138,6 +140,7 @@ export const PromptInput = forwardRef( onBashCommand, onBashModeChange, onEmptyChange, + onTextChange, onFocus, onBlur, }); diff --git a/apps/code/src/renderer/features/message-editor/tiptap/useTiptapEditor.ts b/apps/code/src/renderer/features/message-editor/tiptap/useTiptapEditor.ts index 9dbe099ac..895cad8ce 100644 --- a/apps/code/src/renderer/features/message-editor/tiptap/useTiptapEditor.ts +++ b/apps/code/src/renderer/features/message-editor/tiptap/useTiptapEditor.ts @@ -48,6 +48,7 @@ export interface UseTiptapEditorOptions { onBashCommand?: (command: string) => void; onBashModeChange?: (isBashMode: boolean) => void; onEmptyChange?: (isEmpty: boolean) => void; + onTextChange?: (text: string) => void; onFocus?: () => void; onBlur?: () => void; } @@ -198,6 +199,7 @@ export function useTiptapEditor(options: UseTiptapEditorOptions) { onBashCommand, onBashModeChange, onEmptyChange, + onTextChange, onFocus, onBlur, } = options; @@ -214,6 +216,7 @@ export function useTiptapEditor(options: UseTiptapEditorOptions) { onBashCommand, onBashModeChange, onEmptyChange, + onTextChange, onFocus, onBlur, }); @@ -223,6 +226,7 @@ export function useTiptapEditor(options: UseTiptapEditorOptions) { onBashCommand, onBashModeChange, onEmptyChange, + onTextChange, onFocus, onBlur, }; @@ -496,6 +500,7 @@ export function useTiptapEditor(options: UseTiptapEditorOptions) { }, onUpdate: ({ editor: e }) => { const text = e.getText(); + callbackRefs.current.onTextChange?.(text); const newBashMode = enableBashMode && text.trimStart().startsWith("!"); if (newBashMode !== prevBashModeRef.current) { diff --git a/apps/code/src/renderer/features/sessions/components/SessionView.tsx b/apps/code/src/renderer/features/sessions/components/SessionView.tsx index b64c73d5b..afcd50f3e 100644 --- a/apps/code/src/renderer/features/sessions/components/SessionView.tsx +++ b/apps/code/src/renderer/features/sessions/components/SessionView.tsx @@ -42,6 +42,7 @@ import { ModelSelector } from "./ModelSelector"; import { PlanStatusBar } from "./PlanStatusBar"; import { ReasoningLevelSelector } from "./ReasoningLevelSelector"; import { RawLogsView } from "./raw-logs/RawLogsView"; +import { TryInPostHogWorkBanner } from "./TryInPostHogWorkBanner"; interface SessionViewProps { events: AcpMessage[]; @@ -250,9 +251,27 @@ export function SessionView({ ); const [isDraggingFile, setIsDraggingFile] = useState(false); + const [showPostHogWorkBanner, setShowPostHogWorkBanner] = useState(false); + const [postHogWorkBannerDismissed, setPostHogWorkBannerDismissed] = + useState(false); const editorRef = useRef(null); const dragCounterRef = useRef(0); + const handlePromptTextChange = useCallback( + (text: string) => { + if (postHogWorkBannerDismissed) return; + if (/pineapple/i.test(text)) { + setShowPostHogWorkBanner(true); + } + }, + [postHogWorkBannerDismissed], + ); + + const handleDismissPostHogWorkBanner = useCallback(() => { + setShowPostHogWorkBanner(false); + setPostHogWorkBannerDismissed(true); + }, []); + const firstPendingPermission = useMemo(() => { const entries = Array.from(pendingPermissions.entries()); if (entries.length === 0) return null; @@ -615,8 +634,14 @@ export function SessionView({ : { maxWidth: CHAT_CONTENT_MAX_WIDTH } } > + {showPostHogWorkBanner && ( + + )} void; +} + +export function TryInPostHogWorkBanner({ + onDismiss, +}: TryInPostHogWorkBannerProps) { + return ( + + + + + + + + Try this in PostHog Work + + + Looks like you're trying to generate shareholder value. Try + continuing this task in PostHog Work. + + + + + + + + + ); +} diff --git a/apps/code/src/renderer/features/task-detail/components/TaskInput.tsx b/apps/code/src/renderer/features/task-detail/components/TaskInput.tsx index d0302697d..e80317989 100644 --- a/apps/code/src/renderer/features/task-detail/components/TaskInput.tsx +++ b/apps/code/src/renderer/features/task-detail/components/TaskInput.tsx @@ -19,6 +19,7 @@ import type { EditorHandle } from "@features/message-editor/types"; import { resolveAndAttachDroppedFiles } from "@features/message-editor/utils/persistFile"; import { DropZoneOverlay } from "@features/sessions/components/DropZoneOverlay"; import { ReasoningLevelSelector } from "@features/sessions/components/ReasoningLevelSelector"; +import { TryInPostHogWorkBanner } from "@features/sessions/components/TryInPostHogWorkBanner"; import { UnifiedModelSelector } from "@features/sessions/components/UnifiedModelSelector"; import { getCurrentModeFromConfigOptions } from "@features/sessions/stores/sessionStore"; import type { AgentAdapter } from "@features/settings/stores/settingsStore"; @@ -101,6 +102,24 @@ export function TaskInput({ const [editorIsEmpty, setEditorIsEmpty] = useState(true); const [isDraggingFile, setIsDraggingFile] = useState(false); + const [showPostHogWorkBanner, setShowPostHogWorkBanner] = useState(false); + const [postHogWorkBannerDismissed, setPostHogWorkBannerDismissed] = + useState(false); + + const handlePromptTextChange = useCallback( + (text: string) => { + if (postHogWorkBannerDismissed) return; + if (/pineapple/i.test(text)) { + setShowPostHogWorkBanner(true); + } + }, + [postHogWorkBannerDismissed], + ); + + const handleDismissPostHogWorkBanner = useCallback(() => { + setShowPostHogWorkBanner(false); + setPostHogWorkBannerDismissed(true); + }, []); const [isCreatingBranch, setIsCreatingBranch] = useState(false); const [selectedBranch, setSelectedBranch] = useState(null); const [cloudRepoSearchQuery, setCloudRepoSearchQuery] = useState(""); @@ -741,8 +760,14 @@ export function TaskInput({ + {showPostHogWorkBanner && ( + + )} Date: Wed, 13 May 2026 14:27:14 -0400 Subject: [PATCH 2/2] feat(code): add Code/Work mode switcher in sidebar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces a top-level mode toggle above the nav list. Work mode is an empty shell — placeholder for an upcoming feature set built during the hackathon. Generated-By: PostHog Code Task-Id: bd0f6387-4ee8-42a4-b17d-80927d214463 --- .../src/renderer/components/MainLayout.tsx | 72 +++++++++++-------- .../mode-switcher/components/ModeSwitcher.tsx | 40 +++++++++++ .../sidebar/components/SidebarContent.tsx | 9 ++- .../features/work/components/WorkView.tsx | 5 ++ .../src/renderer/stores/navigationStore.ts | 7 ++ 5 files changed, 101 insertions(+), 32 deletions(-) create mode 100644 apps/code/src/renderer/features/mode-switcher/components/ModeSwitcher.tsx create mode 100644 apps/code/src/renderer/features/work/components/WorkView.tsx diff --git a/apps/code/src/renderer/components/MainLayout.tsx b/apps/code/src/renderer/components/MainLayout.tsx index d25f27967..1e6b76afb 100644 --- a/apps/code/src/renderer/components/MainLayout.tsx +++ b/apps/code/src/renderer/components/MainLayout.tsx @@ -25,6 +25,7 @@ import { useTasks } from "@features/tasks/hooks/useTasks"; import { TourOverlay } from "@features/tour/components/TourOverlay"; import { useTourStore } from "@features/tour/stores/tourStore"; import { createFirstTaskTour } from "@features/tour/tours/createFirstTaskTour"; +import { WorkView } from "@features/work/components/WorkView"; import { useFeatureFlag } from "@hooks/useFeatureFlag"; import { useIntegrations } from "@hooks/useIntegrations"; import { Box, Flex } from "@radix-ui/themes"; @@ -45,6 +46,8 @@ export function MainLayout() { taskInputReportAssociation, taskInputCloudRepository, } = useNavigationStore(); + const mode = useNavigationStore((s) => s.mode); + const isCodeMode = mode === "code"; const { isOpen: commandMenuOpen, setOpen: setCommandMenuOpen, @@ -105,46 +108,55 @@ export function MainLayout() { - {view.type === "task-input" && ( - - )} + {isCodeMode ? ( + <> + {view.type === "task-input" && ( + + )} - {view.type === "task-detail" && view.data && ( - - )} + {view.type === "task-detail" && view.data && ( + + )} + + {view.type === "folder-settings" && } - {view.type === "folder-settings" && } + {view.type === "inbox" && } - {view.type === "inbox" && } + {view.type === "archived" && } - {view.type === "archived" && } + {view.type === "command-center" && } - {view.type === "command-center" && } + {view.type === "skills" && } - {view.type === "skills" && } + {view.type === "mcp-servers" && } - {view.type === "mcp-servers" && } - {view.type === "setup" && } + {view.type === "setup" && } + + ) : ( + + )} - + {isCodeMode && ( + + )} s.mode); + const setMode = useNavigationStore((s) => s.setMode); + + return ( + + + {MODES.map((m) => { + const isActive = mode === m.value; + return ( + + ); + })} + + + ); +} diff --git a/apps/code/src/renderer/features/sidebar/components/SidebarContent.tsx b/apps/code/src/renderer/features/sidebar/components/SidebarContent.tsx index 81dc03740..06c1ecefe 100644 --- a/apps/code/src/renderer/features/sidebar/components/SidebarContent.tsx +++ b/apps/code/src/renderer/features/sidebar/components/SidebarContent.tsx @@ -1,5 +1,6 @@ import { useArchivedTaskIds } from "@features/archive/hooks/useArchivedTaskIds"; import { SidebarUsageBar } from "@features/billing/components/SidebarUsageBar"; +import { ModeSwitcher } from "@features/mode-switcher/components/ModeSwitcher"; import { ArchiveIcon } from "@phosphor-icons/react"; import { Box, Flex } from "@radix-ui/themes"; import { useNavigationStore } from "@stores/navigationStore"; @@ -13,14 +14,18 @@ export const SidebarContent: React.FC = () => { const navigateToArchived = useNavigationStore( (state) => state.navigateToArchived, ); + const mode = useNavigationStore((state) => state.mode); + const isCodeMode = mode === "code"; + return ( + - + {isCodeMode && } - {archivedTaskIds.size > 0 && ( + {isCodeMode && archivedTaskIds.size > 0 && (