Files
supabase/apps/docs/lib/partials.utils.ts
Nik Richers d46cc88f09 docs: add agent prompts to all 18 framework quickstarts (#47543)
## 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?

Docs enhancement: Agent-ready prompt blocks on all 18 framework
quickstart pages.

## What is the current behavior?

Framework quickstarts do not surface a copyable AI prompt. Readers have
to assemble context themselves when asking an AI coding assistant to
follow the guide.

## What is the new behavior?

- Partials at
`apps/docs/content/_partials/ai/quickstart_prompt_{framework}.mdx`
contain `<AiPrompt prompt={...} />` (Prettier multiline single-quoted JS
string with `\n` escapes).
- Each quickstart includes `<$Partial
path="ai/quickstart_prompt_{framework}.mdx" />`.
- Runtime: `AiPrompt` → `PromptPanel` (Copy AI Prompt, expandable).
- Markdown export: `apps/docs/internals/markdown-schema/AiPrompt.ts`
decodes Prettier single-quoted prompt expressions so exported markdown
includes an **AI Prompt** section without quote leak.
- Shared `$Partial` helpers live in `lib/partials.utils.ts`.
- Closes DOCS-1144.

### Example before/after

| | Production | Preview |
| --- | --- | --- |
| Next.js quickstart |
[production](https://supabase.com/docs/guides/getting-started/quickstarts/nextjs)
|
[preview](https://docs-git-nikrichers-docs-1144-add-ai-prompt-blo-5af4d8-supabase.vercel.app/docs/guides/getting-started/quickstarts/nextjs)
|

**Light**

| Before | After |
| --- | --- |
| ![before
light](https://moijyfpvgnmgoxvwcikq.supabase.co/storage/v1/object/public/pr-proof/supabase/supabase/pr47543/quickstart-nextjs-before-light-d1076045.png)
| ![after
light](https://moijyfpvgnmgoxvwcikq.supabase.co/storage/v1/object/public/pr-proof/supabase/supabase/pr47543/quickstart-nextjs-after-light-74996d6c.png)
|

**Dark**

| Before | After |
| --- | --- |
| ![before
dark](https://moijyfpvgnmgoxvwcikq.supabase.co/storage/v1/object/public/pr-proof/supabase/supabase/pr47543/quickstart-nextjs-before-dark-faa391c3.png)
| ![after
dark](https://moijyfpvgnmgoxvwcikq.supabase.co/storage/v1/object/public/pr-proof/supabase/supabase/pr47543/quickstart-nextjs-after-dark-56d1578a.png)
|

### Test plan

- [x] Preview renders AI Prompt panel with copy
- [x] Spot-check Next.js, Flutter, Expo, Vue
- [x] `test-quickstart-prompts` structural
- [x] Markdown export includes **AI Prompt** without quote leak
- [x] Format CI green after prettier/single-quote decode fix

## Additional context

- Worktree:
`~/GitHub/supabase/supabase-worktrees/nikrichers/docs-1144-add-ai-prompt-blocks-to-all-18-framework-quickstarts`
- Skills: `generate-quickstart-prompts` / `test-quickstart-prompts`;
librarian update https://github.com/supabase/docs-agent-skills/pull/21
- `PromptPanel` replaced the older GlassPanel experiment for the
expandable copy UI

---------

Co-authored-by: Nik Richers <nik@validmind.ai>
Co-authored-by: jeremenichelli <jeremenichelli@users.noreply.github.com>
2026-07-24 22:19:04 +00:00

71 lines
2.2 KiB
TypeScript

/**
* Shared $Partial variable substitution for the MDX runtime pipeline and the
* markdown export pipeline.
*/
/**
* Substitutes provided variables into partial content. Variable substitution is
* optional: any variable referenced in the content but not provided is replaced
* with an empty string, and any variable provided but not referenced is ignored.
* A leading `\` escape (`\{{ .var }}`) opts a placeholder out of substitution.
*/
export function substitutePartialVars(
content: string,
vars: Record<string, string> | undefined
): string {
for (const [key, value] of Object.entries(vars ?? {})) {
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
content = content.replace(
new RegExp(`(?<!\\\\)\\{\\{\\s*\\.${escapedKey}\\s*\\}\\}`, 'g'),
() => value
)
}
content = content.replace(/(?<!\\)\{\{\s*\.[\w-]+\s*\}\}/g, '')
return content
}
function assertStringValues(parsed: unknown): asserts parsed is Record<string, string> {
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
throw new Error('Invalid $Partial variables: must be valid JSON containing only string values')
}
for (const value of Object.values(parsed)) {
if (typeof value !== 'string') {
throw new Error(
'Invalid $Partial variables: must be valid JSON containing only string values'
)
}
}
}
/**
* Parses the `variables` prop from a `$Partial` node. Accepts a JSON object
* string (from MDX attribute expressions) or an already-parsed object.
*/
export function parsePartialVariables(raw: unknown): Record<string, string> | undefined {
if (raw === undefined || raw === null || raw === true) {
return undefined
}
if (typeof raw === 'object') {
assertStringValues(raw)
return raw
}
if (typeof raw !== 'string') {
throw new Error('Invalid $Partial variables: must be valid JSON containing only string values')
}
try {
const parsed: unknown = JSON.parse(raw)
assertStringValues(parsed)
return parsed
} catch (error) {
if (error instanceof Error && error.message.startsWith('Invalid $Partial variables')) {
throw error
}
throw new Error('Invalid $Partial variables: must be valid JSON containing only string values')
}
}