chore(launch): reconcile README/store live-status + add demo-funnel analytics (#111)
Two scoped changes for the no-spend growth launch (Growth Launch Kit,
Notion page 3a1ac25eb49f81099cc9f3a4286c8ec4):
1. README.md and distribution/play-listing.md said Google Play was
"coming soon" / internal-testing-only, while distribution/retention-analysis.md
and the live play.google.com listing show it's actually public with 1K+
installs. Fixed the contradiction, added Google Play as a third install
channel, and added an accurate mention of the new offline demo mode
("Try a Demo" — reasoning, grep, diff, permission prompt, ~30s, no server)
matching what app/demo.tsx + src/lib/demo-script.ts actually render.
play-listing.md's stale pre-launch checklists are marked historical
instead of rewritten, so #83's ASO copy/keyword work is untouched.
2. Added the demo funnel's key metric (demo-completion, per the launch
kit) as four consent-gated PostHog events: demo_started,
demo_step_advanced, demo_completed, demo_exited_to_connect. Pure
property-derivation logic lives in src/lib/demo-analytics.ts (no
RN/PostHog imports, unit-tested with node --test, same pattern as
analytics-classify.ts) and is wired into app/demo.tsx's lifecycle.
Updated docs/analytics.md's event table and the privacy policy's event
list (distribution/privacy-policy.md + its two HTML mirrors) per the
repo's "new event requires a policy update" convention.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
31
app/demo.tsx
31
app/demo.tsx
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useMemo, useState } from "react"
|
||||
import { useCallback, useEffect, useMemo, useState } from "react"
|
||||
import { View, Text, ScrollView, TouchableOpacity, StyleSheet, useColorScheme, Linking } from "react-native"
|
||||
import { Stack, useRouter } from "expo-router"
|
||||
import { Ionicons } from "@expo/vector-icons"
|
||||
@@ -6,6 +6,13 @@ import { useTranslation } from "react-i18next"
|
||||
import { MessageBubble, PermissionPrompt } from "../src/components/chat"
|
||||
import { buildDemoScript, buildDemoCompletionMessage, buildDemoDenialMessage } from "../src/lib/demo-script"
|
||||
import { SETUP_GUIDE_URL } from "../src/lib/links"
|
||||
import { track, AnalyticsEvent } from "../src/lib/analytics"
|
||||
import {
|
||||
demoStepAdvancedProps,
|
||||
demoCompletedOutcome,
|
||||
demoExitedToConnectProps,
|
||||
type DemoPermissionReply,
|
||||
} from "../src/lib/demo-analytics"
|
||||
|
||||
// Fully offline, scripted walkthrough for installers with no self-hosted
|
||||
// opencode server. ISOLATION: every message/part below is hardcoded local
|
||||
@@ -22,12 +29,26 @@ export default function DemoScreen() {
|
||||
|
||||
// Built once per mount from pure, hardcoded data (src/lib/demo-script.ts).
|
||||
const script = useMemo(() => buildDemoScript(), [])
|
||||
const [reply, setReply] = useState<"once" | "always" | "reject" | null>(null)
|
||||
const [reply, setReply] = useState<DemoPermissionReply | null>(null)
|
||||
|
||||
const handleReply = useCallback((r: "once" | "always" | "reject") => {
|
||||
setReply(r)
|
||||
// Fires once per screen mount — this is a fully offline, consent-gated
|
||||
// event (track() is a no-op without telemetry consent, same as every
|
||||
// other analytics call site).
|
||||
useEffect(() => {
|
||||
track(AnalyticsEvent.DemoStarted)
|
||||
}, [])
|
||||
|
||||
const handleReply = useCallback((r: DemoPermissionReply) => {
|
||||
setReply(r)
|
||||
track(AnalyticsEvent.DemoStepAdvanced, demoStepAdvancedProps(r))
|
||||
track(AnalyticsEvent.DemoCompleted, { outcome: demoCompletedOutcome(r) })
|
||||
}, [])
|
||||
|
||||
const handleConnectPress = useCallback(() => {
|
||||
track(AnalyticsEvent.DemoExitedToConnect, demoExitedToConnectProps(reply !== null))
|
||||
router.push("/connection/add")
|
||||
}, [reply, router])
|
||||
|
||||
const completion = useMemo(() => {
|
||||
if (!reply) return null
|
||||
return reply === "reject" ? buildDemoDenialMessage() : buildDemoCompletionMessage()
|
||||
@@ -59,7 +80,7 @@ export default function DemoScreen() {
|
||||
<Text style={[s.ctaSubtitle, isDark && s.metaDark]}>{t("demo.ctaSubtitle")}</Text>
|
||||
<TouchableOpacity
|
||||
style={s.connectButton}
|
||||
onPress={() => router.push("/connection/add")}
|
||||
onPress={handleConnectPress}
|
||||
testID="demo-connect-button"
|
||||
>
|
||||
<Text style={s.connectButtonText}>{t("demo.connectButton")}</Text>
|
||||
|
||||
Reference in New Issue
Block a user