Skip to content
This repository was archived by the owner on Aug 6, 2026. It is now read-only.

Commit e9cea7b

Browse files
refactor(mobile): adopt shared presentation semantics
Generated-By: PostHog Code Task-Id: c1bbe3cf-742b-4b24-bf96-d11a18b4cf22
1 parent f2fe45d commit e9cea7b

42 files changed

Lines changed: 134 additions & 768 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/mobile/README.md

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ pnpm --filter @posthog/mobile start
3232

3333
### Feature Folders
3434

35-
Code is organized by feature in `src/features/`. Each feature is self-contained with its own components, hooks, stores, and API logic.
35+
Code is organized by feature in `src/features/`. Features own native components, one-source hooks, and view state. They do not own copies of cloud contracts, transport, orchestration, or presentation rules.
3636

3737
```
3838
src/features/
@@ -46,18 +46,31 @@ src/features/
4646
│ ├── hooks/
4747
│ ├── stores/
4848
│ └── types.ts
49-
├── conversations/ # PostHog AI conversation list & management
50-
│ ├── api.ts
49+
├── inbox/ # Native inbox rendering and query hooks
5150
│ ├── components/
5251
│ ├── hooks/
5352
│ └── stores/
54-
└── tasks/ # Task management
55-
├── api.ts
53+
└── tasks/ # Native cloud-task rendering and host adapters
5654
├── components/
5755
├── hooks/
56+
├── services/
5857
└── stores/
5958
```
6059

60+
### Portability boundary
61+
62+
Mobile and desktop use the same cloud-task architecture. New work must preserve these ownership rules:
63+
64+
- `@posthog/shared` owns runtime contracts and Zod schemas.
65+
- `@posthog/api-client` owns authenticated PostHog HTTPS transport and its request/response types.
66+
- `@posthog/core` owns cloud-task orchestration and headless presentation decisions, including sessions, queues, permissions, models, repositories, inbox rules, and automation semantics.
67+
- `apps/mobile` owns Expo lifecycle, React Native rendering, gestures, sheets, notifications, audio, secure storage, and small persisted view-state stores.
68+
- `@posthog/ui` owns the DOM/Quill renderer and web view state.
69+
70+
Do not add a mobile API facade, duplicate a shared type, or re-export a core helper through a mobile file. Import the owning package directly. If desktop and mobile need different visuals, add a headless descriptor or decision function to core and keep two thin renderers.
71+
72+
Intentional host differences are limited to platform capabilities and view state. Mobile may persist native navigation state, cached picker snapshots, optimistic attachment echoes, and notification preferences; it must not implement retries, reconnection, transport parsing, task lifecycle, or cross-store decisions in those stores.
73+
6174
### File-Based Routing
6275

6376
Routes for the screens are defined by the file structure in `src/app/` using expo-router.

apps/mobile/src/app/(tabs)/inbox.tsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { buildInboxViewedProperties } from "@posthog/core/inbox/engagement";
12
import { INBOX_PIPELINE_STATUSES } from "@posthog/core/inbox/reportFiltering";
23
import type { SignalReport } from "@posthog/shared/domain-types";
34
import { useFocusEffect, useRouter } from "expo-router";
@@ -24,7 +25,6 @@ import {
2425
} from "@/features/inbox/stores/dismissedReportsStore";
2526
import { useInboxFilterStore } from "@/features/inbox/stores/inboxFilterStore";
2627
import { useInboxStore } from "@/features/inbox/stores/inboxStore";
27-
import { buildInboxViewedProperties } from "@/features/inbox/utils";
2828
import { useIntegrations } from "@/features/tasks/hooks/useIntegrations";
2929
import { ANALYTICS_EVENTS, useAnalytics } from "@/lib/analytics";
3030

@@ -67,12 +67,16 @@ export default function InboxScreen() {
6767
viewedFiredForFocusRef.current = focusVersion;
6868
analytics.track(
6969
ANALYTICS_EVENTS.INBOX_VIEWED,
70-
buildInboxViewedProperties(reports, totalCount, {
71-
sourceProductFilter,
72-
statusFilter,
73-
suggestedReviewerFilter,
74-
priorityFilter,
75-
defaultStatusFilter: INBOX_PIPELINE_STATUSES,
70+
buildInboxViewedProperties({
71+
visibleReports: reports,
72+
totalCount,
73+
filters: {
74+
sourceProductFilter,
75+
statusFilter,
76+
suggestedReviewerFilter,
77+
priorityFilter,
78+
defaultStatusFilter: INBOX_PIPELINE_STATUSES,
79+
},
7680
}),
7781
);
7882
}, [

apps/mobile/src/app/automation/[id].tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Text } from "@components/text";
22
import { TaskAutomationValidationError } from "@posthog/api-client/posthog-client";
3+
import { parseSkillTemplateId } from "@posthog/core/automations/automationTemplatePresentation";
34
import { Stack, useLocalSearchParams, useRouter } from "expo-router";
45
import { useState } from "react";
56
import {
@@ -20,7 +21,6 @@ import {
2021
useUpdateTaskAutomation,
2122
} from "@/features/tasks/hooks/useAutomations";
2223
import { useTask } from "@/features/tasks/hooks/useTasks";
23-
import { parseSkillTemplateId } from "@/features/tasks/skills/skillTemplateIds";
2424
import { useThemeColors } from "@/lib/theme";
2525

2626
export default function AutomationDetailScreen() {

apps/mobile/src/app/automation/create.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { TaskAutomationValidationError } from "@posthog/api-client/posthog-client";
2+
import { formatSkillTemplateId } from "@posthog/core/automations/automationTemplatePresentation";
23
import { getCalendars } from "expo-localization";
34
import { Stack, useLocalSearchParams, useRouter } from "expo-router";
45
import { useMemo, useRef, useState } from "react";
@@ -14,7 +15,6 @@ import { Text } from "@/components/text";
1415
import { AutomationForm } from "@/features/tasks/components/AutomationForm";
1516
import { useCreateTaskAutomation } from "@/features/tasks/hooks/useAutomations";
1617
import { useSkillStoreSkill } from "@/features/tasks/skills/hooks";
17-
import { formatSkillTemplateId } from "@/features/tasks/skills/skillTemplateIds";
1818
import { useScreenInsets } from "@/hooks/useScreenInsets";
1919
import { useThemeColors } from "@/lib/theme";
2020

apps/mobile/src/app/mcp-servers/add-custom.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Text } from "@components/text";
2+
import type { McpAuthType } from "@posthog/api-client/types";
23
import { router } from "expo-router";
34
import { Lock } from "phosphor-react-native";
45
import { useState } from "react";
@@ -14,7 +15,6 @@ import {
1415
import { FloatingMcpHeader } from "@/features/mcp/components/FloatingMcpHeader";
1516
import { useMcpInstallations } from "@/features/mcp/hooks";
1617
import { installCustomWithOAuth } from "@/features/mcp/oauth";
17-
import type { McpAuthType } from "@/features/mcp/types";
1818
import { useScreenInsets } from "@/hooks/useScreenInsets";
1919
import { logger } from "@/lib/logger";
2020
import { useThemeColors } from "@/lib/theme";

apps/mobile/src/app/mcp-servers/index.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
import { Text } from "@components/text";
2+
import type {
3+
McpRecommendedServer,
4+
McpServerInstallation,
5+
} from "@posthog/api-client/types";
26
import { useRouter } from "expo-router";
37
import { MagnifyingGlass, Plus, PuzzlePiece } from "phosphor-react-native";
48
import { useMemo, useState } from "react";
@@ -17,10 +21,6 @@ import {
1721
recommendedToRowProps,
1822
} from "@/features/mcp/components/McpServerRow";
1923
import { useMcpInstallations, useMcpMarketplace } from "@/features/mcp/hooks";
20-
import type {
21-
McpRecommendedServer,
22-
McpServerInstallation,
23-
} from "@/features/mcp/types";
2424
import { useScreenInsets } from "@/hooks/useScreenInsets";
2525
import { useThemeColors } from "@/lib/theme";
2626

apps/mobile/src/app/mcp-servers/installation/[id].tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { Text } from "@components/text";
2+
import type { McpApprovalState } from "@posthog/api-client/types";
3+
import { isStdioMcpServer } from "@posthog/core/mcp-servers/presentation";
24
import { router, useLocalSearchParams } from "expo-router";
35
import {
46
ArrowsClockwise,
@@ -28,8 +30,6 @@ import {
2830
} from "@/features/mcp/hooks";
2931
import { reauthorizeInstallation } from "@/features/mcp/oauth";
3032
import { getMcpConnectionManager } from "@/features/mcp/service";
31-
import type { McpApprovalState } from "@/features/mcp/types";
32-
import { isStdioServer } from "@/features/mcp/types";
3333
import { useScreenInsets } from "@/hooks/useScreenInsets";
3434
import { logger } from "@/lib/logger";
3535
import { useThemeColors } from "@/lib/theme";
@@ -75,7 +75,7 @@ export default function McpInstallationDetailScreen() {
7575
);
7676
}
7777

78-
const stdio = isStdioServer(installation);
78+
const stdio = isStdioMcpServer(installation);
7979

8080
const handleEnabledChange = (enabled: boolean) => {
8181
updateMutation.mutate({

apps/mobile/src/app/mcp-servers/template/[id].tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Text } from "@components/text";
2+
import { isStdioMcpServer } from "@posthog/core/mcp-servers/presentation";
23
import { router, useLocalSearchParams } from "expo-router";
34
import { Lock, Warning } from "phosphor-react-native";
45
import { useMemo, useState } from "react";
@@ -17,7 +18,6 @@ import {
1718
useMcpMarketplace,
1819
} from "@/features/mcp/hooks";
1920
import { installTemplateWithOAuth } from "@/features/mcp/oauth";
20-
import { isStdioServer } from "@/features/mcp/types";
2121
import { useScreenInsets } from "@/hooks/useScreenInsets";
2222
import { logger } from "@/lib/logger";
2323
import { openExternalUrl } from "@/lib/openExternalUrl";
@@ -71,7 +71,7 @@ export default function McpTemplateDetailScreen() {
7171
);
7272
}
7373

74-
const stdio = isStdioServer(template);
74+
const stdio = isStdioMcpServer(template);
7575

7676
const handleInstall = async () => {
7777
if (!template) return;

apps/mobile/src/features/chat/components/AgentMessage.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1+
import { pickThinkingActivity } from "@posthog/core/sessions/thinkingActivities";
12
import { Brain } from "phosphor-react-native";
23
import { useState } from "react";
34
import { Pressable, Text, View } from "react-native";
45
import { formatRelativeTime } from "@/lib/format";
56
import { useThemeColors } from "@/lib/theme";
67
import { usePeriodicRerender } from "../hooks/usePeriodicRerender";
7-
import { getRandomThinkingMessage } from "../utils/thinkingMessages";
88
import { CopyButton } from "./CopyButton";
99
import { MarkdownText } from "./MarkdownText";
1010
import { ToolMessage } from "./ToolMessage";
@@ -110,7 +110,7 @@ export function AgentMessage({
110110
{isLoading && !content && !thinkingText && (
111111
<View className="max-w-[95%] px-4 py-1">
112112
<Text className="font-mono text-[13px] text-gray-9 italic">
113-
{getRandomThinkingMessage()}
113+
{pickThinkingActivity(Math.random())}...
114114
</Text>
115115
</View>
116116
)}

apps/mobile/src/features/chat/components/ToolMessage.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
import {
2+
formatPosthogExecBody,
3+
getPostHogExecDisplay,
4+
isPostHogExecTool,
5+
} from "@posthog/core/sessions/posthogExecDisplay";
6+
import { parseMcpToolName } from "@posthog/shared";
17
import { useRouter } from "expo-router";
28
import {
39
ArrowsClockwise,
@@ -22,13 +28,7 @@ import {
2228
TouchableOpacity,
2329
View,
2430
} from "react-native";
25-
import {
26-
formatPosthogExecBody,
27-
getPostHogExecDisplay,
28-
isPostHogExecTool,
29-
} from "@/features/chat/utils/posthogExecDisplay";
3031
import { McpAppHost } from "@/features/mcp/components/McpAppHost";
31-
import { isMcpToolName } from "@/features/mcp/utils/mcpToolName";
3232
import {
3333
getColorForClass,
3434
highlightCode,
@@ -942,7 +942,8 @@ export function ToolMessage({
942942
// MCP App tools render via the WebView host — skip PostHog exec (which has
943943
// its own renderer above) and only kick in once the tool finished or while
944944
// it's running so we don't show empty WebView shells for pending tools.
945-
const isMcpAppTool = !isPostHogExec && isMcpToolName(effectiveToolName);
945+
const isMcpAppTool =
946+
!isPostHogExec && parseMcpToolName(effectiveToolName) !== undefined;
946947

947948
if (isMcpAppTool && !isPending) {
948949
return (

0 commit comments

Comments
 (0)