Files
Nik RichersandNik Richers e3c677fc5a feat(docs): track prompt panel copies in PostHog (#50482)
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

Add telemetry for `PromptPanel` to help us understand how people
interact with our AI prompts better.

 Relates to DOCS-1393

Dashboard(restricted access): [Docs: AI prompt
affordances](https://eu.posthog.com/project/34344/dashboard/957235)

## What is the current behavior?

The docs homepage cover renders a setup panel with "AI Prompt" and "CLI"
tabs, and guides render `AiPrompt` blocks. Both are built on the shared
`PromptPanel`, whose copy button called `copyToClipboard` and nothing
else. Copying was therefore unmeasured, while the neighbouring
affordances (`ask_ai_clicked`, `agent_setup_clicked`,
`copy_as_markdown_clicked`) are already instrumented.

## What is the new behavior?

`PromptPanel` takes an optional `telemetry` prop. When it is set, the
panel sends a new docs-owned event after a **successful** clipboard
write, so instrumentation lives in the shared component instead of a
forked homepage copy button.

New event in `packages/common/telemetry-constants.ts`:

| | |
| --- | --- |
| `action` | `docs_ai_prompt_copied` |
| `source` | `homepage` \| `guide` \| `agent_setup` |
| `tab` | `prompt` \| `cli` (omitted for panes outside that set) |
| `promptId` | prompt id, when the panel comes from an `AiPrompt` block
|

Wired consumers: `HomePageCover` (`homepage`), `AiPrompt` (`guide` by
default, plus `promptId`), and `AgentSetup` (`agent_setup`). No prompt
body text and no PII is sent.

Studio's existing `ai_prompt_copied` event is deliberately left alone:
it has a different owner and surface, and merging the two would blend
unrelated funnels.

### Proof it works

```
$ pnpm run test:local:unwatch features/ui/PromptPanel.telemetry.test.ts

 RUN  v5.0.0 /apps/docs

 Test Files  1 passed (1)
      Tests  4 passed (4)
   Duration  775ms
```

## Additional context

Test plan, run against a local docs server with a stub telemetry
endpoint so the request bodies could be read directly:

| Case | Observed payload |
| --- | --- |
| Homepage, AI Prompt tab | `{"source":"homepage","tab":"prompt"}` |
| Homepage, CLI tab | `{"source":"homepage","tab":"cli"}` |
| Next.js quickstart `AiPrompt` |
`{"source":"guide","tab":"prompt","promptId":"nextjs"}` |
| `automate-with-agents/health` `AgentSetup` |
`{"source":"agent_setup","tab":"prompt","promptId":"monitoring-agent-health"}`
|
| Clipboard write rejected | no request sent, error toast shown, button
does not flip to "copied" |

The failure case was re-checked with a control click on the same page
after restoring a working clipboard, which did send the event, so the
negative result is not just a missed handler.

Also run: `turbo typecheck --filter=docs --filter=common` (passes),
Prettier check on the touched files (passes), and ESLint on the touched
docs files (no new findings; the one warning on `HomePageCover` is the
pre-existing default export).

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Successful prompt copies are now tracked across the homepage,
documentation guides, and agent setup experiences.
* Copy activity records the prompt’s source, selected format, and
associated prompt when available, providing more complete usage
insights.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Nik Richers <nik@validmind.ai>
2026-09-20 17:08:03 +00:00

54 lines
1.5 KiB
TypeScript

'use client'
import { aiPrompts, type AiPromptId } from '~/data/ai-prompts.data'
import {
Prompt,
PromptContent,
PromptCopy,
PromptMarkdown,
PromptPanel,
PromptTitle,
type PromptPanelTelemetry,
} from './PromptPanel'
type AiPromptProps = {
/** Looks up prompt text from `aiPrompts`. */
id: AiPromptId | string
/** Includes the prompt body in generated guide Markdown. */
includeInMarkdown?: boolean
/** Surface reported when the prompt is copied. */
telemetry: Omit<PromptPanelTelemetry, 'promptId'>
}
/**
* MDX-facing wrapper around {@link PromptPanel}. Composes the compound
* children in this client module so detection does not cross the RSC
* boundary (where child types arrive as `react.lazy` and the panel
* would otherwise render nothing).
*
* Prompt text lives in `~/data/ai-prompts.data`. Markdown export is opt-in so
* existing quickstarts do not duplicate their instructions in bulk exports.
*/
function AiPrompt({ id, telemetry }: AiPromptProps) {
const prompt = aiPrompts[id as AiPromptId]
if (!prompt) {
throw new Error(`Unknown AiPrompt id: ${id}`)
}
return (
<PromptPanel telemetry={{ ...telemetry, promptId: id }}>
<Prompt value="prompt" expandable>
<PromptTitle>Agent Prompt</PromptTitle>
<PromptCopy>{prompt}</PromptCopy>
<PromptContent>
<PromptMarkdown>{prompt}</PromptMarkdown>
</PromptContent>
</Prompt>
</PromptPanel>
)
}
export { AiPrompt }
export type { AiPromptProps }