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 && (
+
+ )}
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/mode-switcher/components/ModeSwitcher.tsx b/apps/code/src/renderer/features/mode-switcher/components/ModeSwitcher.tsx
new file mode 100644
index 000000000..d3de906b0
--- /dev/null
+++ b/apps/code/src/renderer/features/mode-switcher/components/ModeSwitcher.tsx
@@ -0,0 +1,40 @@
+import { Box, Flex } from "@radix-ui/themes";
+import { type AppMode, useNavigationStore } from "@stores/navigationStore";
+
+const MODES: { value: AppMode; label: string }[] = [
+ { value: "code", label: "Code" },
+ { value: "work", label: "Work" },
+];
+
+export function ModeSwitcher() {
+ const mode = useNavigationStore((s) => 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/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/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 && (
+ {showPostHogWorkBanner && (
+
+ )}
;
+}
diff --git a/apps/code/src/renderer/stores/navigationStore.ts b/apps/code/src/renderer/stores/navigationStore.ts
index 93a9338c2..ca9e6f0ab 100644
--- a/apps/code/src/renderer/stores/navigationStore.ts
+++ b/apps/code/src/renderer/stores/navigationStore.ts
@@ -35,6 +35,8 @@ interface TaskInputNavigationOptions {
reportAssociation?: TaskInputReportAssociation;
}
+export type AppMode = "code" | "work";
+
interface ViewState {
type: ViewType;
data?: Task;
@@ -47,6 +49,8 @@ interface ViewState {
}
interface NavigationStore {
+ mode: AppMode;
+ setMode: (mode: AppMode) => void;
view: ViewState;
history: ViewState[];
historyIndex: number;
@@ -123,6 +127,8 @@ export const useNavigationStore = create()(
};
return {
+ mode: "code",
+ setMode: (mode: AppMode) => set({ mode }),
view: { type: "task-input" },
history: [{ type: "task-input" }],
historyIndex: 0,
@@ -338,6 +344,7 @@ export const useNavigationStore = create()(
name: "navigation-storage",
storage: electronStorage,
partialize: (state) => ({
+ mode: state.mode,
view: {
type: state.view.type,
taskId: state.view.taskId,