Files
supabase/apps/studio/components/interfaces/ProjectHome/AdvisorSection.tsx
T
Pamela Chia 01c178e159 chore(studio): graduate homeNew experiment (#43437)
## Summary

The `homeNew` PostHog experiment has concluded. This PR graduates it by
making the new homepage (`ProjectHome`, formerly `HomeV2`) the permanent
default for all users, and removes all dead code from the old
experiment.

## Changes

- Remove `homeNew` PostHog feature flag checks and `home_new` experiment
exposure tracking from 3 files
- Rename `HomeNew/` → `ProjectHome/` directory and `HomeV2` →
`ProjectHome` export
- Delete old `Home/Home.tsx` component (shared components like
`ProjectList/` are kept — still used by org pages)
- Delete `pages/project/[ref]/building.tsx` and add a server-side
redirect from `/project/:ref/building` → `/project/:ref` to prevent 404s
during rollout (old cached JS bundles may still route to `/building`)
- Simplify `ContentWrapper` building-state logic in `ProjectLayout` —
always redirect building projects to home, always suppress building
interstitial on home page
- Always route to `/project/{ref}` after project creation (remove
`/building` path)
- Update all Observability imports from `HomeNew` → `ProjectHome`

## Self-hosted behavior change

Self-hosted Studio previously showed the old `Home` component (client
libraries + example projects) since PostHog flags don't load. This PR
changes self-hosted to show `ProjectHome` (TopSection with service
status + instance diagram, advisor, custom reports). All sections query
backend APIs that exist on self-hosted. E2E tests pass against the
self-hosted build.

## Testing

- [x] `pnpm turbo run build --filter=studio` passes
- [x] No remaining references to `homeNew`, `home_new`, or `HomeNew` in
codebase
- [x] No broken imports to deleted files
- [x] Self-hosted E2E tests pass (145 passed, 1 flaky, 4 skipped)
- [x] `/building` redirect added to both platform and self-hosted config
blocks

**Quick test:**
1. Navigate to any project homepage — should render the ProjectHome
component
2. Create a new project — should redirect to `/project/{ref}` (not
`/building`)
3. Visit a project in `COMING_UP` state on a non-home route — should
redirect to home
4. Visit `/project/{ref}/building` directly — should 302 redirect to
`/project/{ref}`

## Linear

- fixes GROWTH-671
2026-03-10 17:03:58 +09:00

177 lines
6.5 KiB
TypeScript

import { useParams } from 'common'
import { LINTER_LEVELS } from 'components/interfaces/Linter/Linter.constants'
import { createLintSummaryPrompt } from 'components/interfaces/Linter/Linter.utils'
import { SIDEBAR_KEYS } from 'components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
import { AiAssistantDropdown } from 'components/ui/AiAssistantDropdown'
import { Lint, useProjectLintsQuery } from 'data/lint/lint-query'
import { useTrack } from 'lib/telemetry/track'
import { BarChart, Shield } from 'lucide-react'
import { useCallback, useMemo } from 'react'
import { useAdvisorStateSnapshot } from 'state/advisor-state'
import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state'
import { useSidebarManagerSnapshot } from 'state/sidebar-manager-state'
import { AiIconAnimation, Button, Card, CardContent, CardHeader, CardTitle } from 'ui'
import { Row } from 'ui-patterns'
import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
import { Markdown } from '../Markdown'
export const AdvisorSection = ({ showEmptyState = false }: { showEmptyState?: boolean }) => {
const { ref: projectRef } = useParams()
const { data: lints, isPending: isLoadingLints } = useProjectLintsQuery(
{
projectRef,
},
{
enabled: !showEmptyState,
}
)
const track = useTrack()
const snap = useAiAssistantStateSnapshot()
const { openSidebar } = useSidebarManagerSnapshot()
const { setSelectedItem } = useAdvisorStateSnapshot()
const errorLints: Lint[] = useMemo(() => {
return lints?.filter((lint) => lint.level === LINTER_LEVELS.ERROR) ?? []
}, [lints])
const totalErrors = errorLints.length
const titleContent = useMemo(() => {
if (totalErrors === 0) return <h2>Advisor found no issues</h2>
const issuesText = totalErrors === 1 ? 'issue' : 'issues'
const numberDisplay = totalErrors.toString()
return (
<h2>
Advisor found {numberDisplay} {issuesText}
</h2>
)
}, [totalErrors])
const handleAskAssistant = useCallback(() => {
openSidebar(SIDEBAR_KEYS.AI_ASSISTANT)
track('advisor_assistant_button_clicked', {
origin: 'homepage',
issuesCount: totalErrors,
})
}, [track, openSidebar, totalErrors])
const handleCardClick = useCallback(
(lint: Lint) => {
setSelectedItem(lint.cache_key, 'lint')
openSidebar(SIDEBAR_KEYS.ADVISOR_PANEL)
track('advisor_detail_opened', {
origin: 'homepage',
advisorSource: 'lint',
advisorCategory: lint.categories[0],
advisorType: lint.name,
advisorLevel: lint.level,
})
},
[track, setSelectedItem, openSidebar]
)
if (showEmptyState) {
return <EmptyState />
}
return (
<div>
{isLoadingLints ? (
<ShimmeringLoader className="w-96 mb-6" />
) : (
<div className="flex justify-between items-center mb-6">
{titleContent}
<Button type="default" icon={<AiIconAnimation />} onClick={handleAskAssistant}>
Ask Assistant
</Button>
</div>
)}
{isLoadingLints ? (
<div className="flex flex-col p-4 gap-2">
<ShimmeringLoader />
<ShimmeringLoader className="w-3/4" />
<ShimmeringLoader className="w-1/2" />
</div>
) : errorLints.length > 0 ? (
<>
<Row columns={[3, 2, 1]}>
{errorLints.map((lint) => {
return (
<Card
key={lint.cache_key}
className="min-h-full flex flex-col items-stretch cursor-pointer h-64"
onClick={() => {
handleCardClick(lint)
}}
>
<CardHeader className="border-b-0 shrink-0 flex flex-row gap-2 space-y-0 justify-between items-center">
<div className="flex flex-row items-center gap-3">
{lint.categories[0] === 'SECURITY' ? (
<Shield size={16} strokeWidth={1.5} className="text-foreground-muted" />
) : (
<BarChart size={16} strokeWidth={1.5} className="text-foreground-muted" />
)}
<CardTitle className="text-foreground-light">{lint.categories[0]}</CardTitle>
</div>
<div
onClick={(e) => {
e.stopPropagation()
e.preventDefault()
}}
>
<AiAssistantDropdown
label="Ask Assistant"
iconOnly
tooltip="Help me fix this issue"
buildPrompt={() => createLintSummaryPrompt(lint)}
onOpenAssistant={() => {
openSidebar(SIDEBAR_KEYS.AI_ASSISTANT)
snap.newChat({
name: 'Summarize lint',
initialInput: createLintSummaryPrompt(lint),
})
track('advisor_assistant_button_clicked', {
origin: 'homepage',
advisorCategory: lint.categories[0],
advisorType: lint.name,
advisorLevel: lint.level,
})
}}
telemetrySource="advisor_section"
type="text"
className="w-7 h-7"
/>
</div>
</CardHeader>
<CardContent className="p-6 pt-16 flex flex-col justify-end flex-1 overflow-auto">
<h3 className="mb-1">{lint.title}</h3>
<Markdown className="leading-6 text-sm text-foreground-light">
{lint.detail && lint.detail.replace(/\\`/g, '`')}
</Markdown>
</CardContent>
</Card>
)
})}
</Row>
</>
) : (
<EmptyState />
)}
</div>
)
}
function EmptyState() {
return (
<Card className="bg-transparent h-64">
<CardContent className="flex flex-col items-center justify-center gap-2 p-16 h-full">
<Shield size={20} strokeWidth={1.5} className="text-foreground-muted" />
<p className="text-sm text-foreground-light text-center">
No security or performance errors found
</p>
</CardContent>
</Card>
)
}