Files
Pamela ChiaandAli Waseem 578a73f966 feat(studio): connectSection experiment — replace Getting Started with Connect section (#43629)
## Summary

Re-ports PR #43119 against the current `ProjectHome` codebase (the
original PR targeted `HomeNew/` which was removed during the `homeNew`
graduation in #43437). Also unifies the `connectSheet` and
`connectSection` feature flags into a single `connectSection` flag — the
`connectSheet` flag is removed entirely, so both the ConnectSheet
(header) and ConnectSection (homepage row) are controlled by one
experiment.

- Adds `connectSection` PostHog experiment flag that controls two
things:
1. Swaps the Getting Started section for a Connect section on the
project homepage for new projects (< 10 days old)
2. Swaps the legacy Connect dialog for the new ConnectSheet panel in the
header
- **Control** (`getting-started`): existing Getting Started section +
legacy Connect dialog in header
- **Treatment** (`connect`): new 4-tile Connect section + ConnectSheet
in header
- `undefined` (loading): neither section renders, avoiding flash
- Tiles filtered by the same `useIsFeatureEnabled` flags as ConnectSheet
(`show_app_frameworks`, `show_mobile_frameworks`, `show_orms`)
- Connect tile clicks tracked via `home_connect_action_clicked`; section
render tracked via `home_connect_section_exposed`; sheet opens tracked
via `connect_sheet_opened` with source attribution (`header_button` or
`connect_section`)

## Changes

- `packages/common/telemetry-constants.ts` — new
`home_connect_section_exposed`, `home_connect_action_clicked`, and
`connect_sheet_opened` event types
- `ConnectSheet/ConnectSheet.tsx` — read `connectTab` query param and
sync to active mode on open; `handleModeChange` keeps param in sync on
tab switch; fire `connect_sheet_opened` event with source attribution on
open
- `ConnectButton/ConnectButton.tsx` — set `connectSource=header_button`
query param on click
- `ProjectHome/ConnectSection.tsx` — new component (4-tile connect
card); set `connectSource=connect_section` on tile click
- `ProjectHome/Home.tsx` — experiment flag wiring
- `LayoutHeader/LayoutHeader.tsx` — read `connectSection` flag instead
of `connectSheet` to toggle ConnectSheet vs legacy Connect dialog

## Test plan

To test on the Vercel preview, set `connectSection=connect` to 100% in
PostHog (or override via cookie `ph_override_connectSection=connect`).
No separate `connectSheet` flag is needed — `connectSection` controls
both features.
- [x] Treatment (`connect` variant) — "Get connected" section renders on
new project, Getting Started hidden
- [x] Control (`getting-started`) — Getting Started renders, Connect
section hidden
- [x] Mature project (> 10 days) — neither section regardless of flag
- [x] Clicking each tile opens ConnectSheet on the correct tab
(Framework / Direct / ORM / MCP)
- [x] Switching tabs inside sheet updates `connectTab` URL param
- [x] Closing sheet clears `connectTab` param
- [x] Direct URL deep-link (`?showConnect=true&connectTab=orm`) opens
sheet on correct tab
- [x] Dark mode — background gradient renders correctly
- [x] Light mode — background switches to light gradient
- [x] Responsive layout — 4 cols (xl), 2×2 (md), stacked (mobile)
- [x] Telemetry — `home_connect_section_exposed` fires once on load;
`home_connect_action_clicked` fires with correct `mode`
- [x] Treatment — header Connect button opens ConnectSheet (not legacy
Connect dialog)
- [x] Control — header Connect button opens legacy Connect dialog (not
ConnectSheet)
- [x] Telemetry — clicking a ConnectSection tile fires
`connect_sheet_opened` with `source: 'connect_section'`
- [x] Telemetry — clicking header Connect button fires
`connect_sheet_opened` with `source: 'header_button'`

---------

Co-authored-by: Ali Waseem <waseema393@gmail.com>
2026-03-13 11:50:07 +09:00

89 lines
2.5 KiB
TypeScript

import { LOCAL_STORAGE_KEYS as COMMON_LOCAL_STORAGE_KEYS } from 'common'
import { proxy, snapshot, useSnapshot } from 'valtio'
const getInitialState = () => {
return {
activeDocsSection: ['introduction'],
docsLanguage: 'js',
showProjectApiDocs: false,
showCreateBranchModal: false,
showAiSettingsModal: false,
showConnectDialog: false,
ongoingQueriesPanelOpen: false,
mobileMenuOpen: false,
showSidebar: true,
showEditorPanel: false,
lastRouteBeforeVisitingAccountPage: '',
}
}
export const appState = proxy({
...getInitialState(),
activeDocsSection: ['introduction'],
docsLanguage: 'js' as 'js' | 'bash',
showProjectApiDocs: false,
setShowProjectApiDocs: (value: boolean) => {
appState.showProjectApiDocs = value
},
setActiveDocsSection: (value: string[]) => {
appState.activeDocsSection = value
},
setDocsLanguage: (value: 'js' | 'bash') => {
appState.docsLanguage = value
},
isOptedInTelemetry: false,
setIsOptedInTelemetry: (value: boolean | null) => {
appState.isOptedInTelemetry = value === null ? false : value
if (typeof window !== 'undefined' && value !== null) {
localStorage.setItem(COMMON_LOCAL_STORAGE_KEYS.TELEMETRY_CONSENT, value.toString())
}
},
isMfaEnforced: false,
setIsMfaEnforced: (value: boolean) => {
appState.isMfaEnforced = value
},
showCreateBranchModal: false,
setShowCreateBranchModal: (value: boolean) => {
appState.showCreateBranchModal = value
},
showAiSettingsModal: false,
setShowAiSettingsModal: (value: boolean) => {
appState.showAiSettingsModal = value
},
showSidebar: true,
setShowSidebar: (value: boolean) => {
appState.showSidebar = value
},
showOngoingQueriesPanelOpen: false,
setOnGoingQueriesPanelOpen: (value: boolean) => {
appState.ongoingQueriesPanelOpen = value
},
mobileMenuOpen: false,
setMobileMenuOpen: (value: boolean) => {
appState.mobileMenuOpen = value
},
connectSheetSource: 'header_button' as 'header_button' | 'connect_section',
setConnectSheetSource: (value: 'header_button' | 'connect_section') => {
appState.connectSheetSource = value
},
lastRouteBeforeVisitingAccountPage: '',
setLastRouteBeforeVisitingAccountPage: (value: string) => {
appState.lastRouteBeforeVisitingAccountPage = value
},
})
export const getAppStateSnapshot = () => snapshot(appState)
export const useAppStateSnapshot = (options?: Parameters<typeof useSnapshot>[1]) =>
useSnapshot(appState, options)