# Content card → keyword prompt generator

**Date:** 2026-06-18
**Status:** design approved, proceeding to implementation
**Scope:** small, reuse-only. Connect the existing content editorial board to the
slice-1 prompt generator. Deliberately minimal — no new data model, action, or
parser.

## Goal

In the content board's card sheet, let a writer generate the ready-to-paste
prompt (grammages YTG + top-10 competitor analysis) for the card's linked
keyword. The card already carries `primaryKeywordId`, and `ContentCardSheet`
already loads that keyword's SERP + brief (SerpPanel/BriefPanel). The only missing
piece is the prompt generator built in slice 1.

## Design

**Extract a reusable presentational-ish component**
`src/components/keywords/planning/keyword-prompt-card.tsx`:
```ts
KeywordPromptCard({ keywordId, bundle }: { keywordId: string; bundle?: KeywordPromptBundle })
```
- If `bundle` is provided, render from it (no fetch). Otherwise self-load via
  `getKeywordPromptBundle(keywordId)` (loading + error/retry states).
- Renders the target selector (Claude Code/Desktop/ChatGPT/Gemini) + task toggle
  (Brief/Rédaction/Optimisation) + the `buildKeywordPrompt` preview (mono, scroll)
  + Régénérer + Copier (sonner toast). Pulled verbatim from the current
  `KeywordOverviewModal` prompt section.

**Refactor `KeywordOverviewModal`** (`keyword-overview-modal.tsx`) to compose
`<KeywordPromptCard bundle={bundle} keywordId={keywordId} />` — it already loads
the bundle for the completeness card, so it passes that bundle down (no double
fetch). Completeness + source-verification stay in the modal. No behaviour change.

**Wire `ContentCardSheet`** (`content-card-sheet.tsx`): when
`item.primaryKeywordId` is set, render a new "Rédaction" section with
`<KeywordPromptCard keywordId={item.primaryKeywordId} />` (self-loading),
alongside the existing Brief/SERP panels. When there is no linked keyword, show a
hint: "Lie un mot-clé pour générer le prompt de rédaction."

## Data flow

Content card open → (existing) loads SERP + briefs for `primaryKeywordId`;
`KeywordPromptCard` independently loads the prompt bundle for the same keyword and
builds the prompt client-side via `buildKeywordPrompt`. All reuse — no new
server action, type, or parser.

## Error handling

- No `primaryKeywordId` → no prompt card, show the hint (content cards can exist
  without a keyword; `primaryKeywordId` is nullable / `set null` on delete).
- Bundle load failure → `KeywordPromptCard` shows an error + Réessayer (logic moves
  with the extraction).

## Testing

The builder + bundle action are already unit-tested. `KeywordPromptCard` is reused
presentational UI → keep it light: a focused test (or manual QA) that the content
sheet shows the Rédaction section when `primaryKeywordId` is present and the hint
when absent. No new heavy tests (anti-over-engineering).

## Implementation fan-out

Contract: `KeywordPromptCard({ keywordId, bundle? })` at the path above.
- W1: create `keyword-prompt-card.tsx` (extract from the modal) + refactor
  `keyword-overview-modal.tsx` to use it.
- W2: wire `content-card-sheet.tsx` to render `<KeywordPromptCard keywordId>` +
  the no-keyword hint.
Run in parallel against the contract; integrate with typecheck + `bun run
test:all` + biome, then commit + ff-merge dev.
