# AI Editor RSP — backend dual-mode SEO-led data/API handoff

Task: `t_aa2aa33a`
Tenant: `aieditorrsp-seo-led-product-reposition-20260605`
Repo: `/root/projects/aieditorrsp`
Site: `https://aieditorrsp.net`
Date: 2026-06-05

## Decision

Implemented the backend/data contract for the SEO-led effect library and dual-mode editor state.

- Copy Prompt is always available from static catalog data.
- Upload-to-Edit is gated by `provider_enabled` and reports an honest disabled reason when the runtime provider is not usable.
- The canonical catalog is the upstream 75-effect JSON from `t_a1024ba2`, now committed into the app as static data for API consumption.
- No D1 schema change was required for this round.

## Files changed

- `src/lib/effect-catalog-seo-led.json`
  - Canonical static catalog copied from `/root/.hermes/reports/aieditorrsp-seo-led-product-reposition-20260605/effect-catalog-seo-led.json`.
  - Contains 75 active effects and 9 homepage featured slugs.
- `src/lib/effects.ts`
  - Typed catalog helpers: list, detail lookup, facets, summary/detail response mappers.
- `src/app/api/effects/route.ts`
  - New effect catalog API.
- `src/app/api/effects/[slug]/route.ts`
  - New effect detail API.
- `src/app/api/editor-config/route.ts`
  - New dual-mode editor state API.
- `src/app/api/prompt-templates/route.ts`
  - Backward-compatible response now includes `legacy_templates` plus SEO-led `templates` from the 75-effect catalog.

## API contract

### `GET /api/effects`

Query params:

- `q`: text search across slug/title/category/summary/search intent/aliases/provider tags/best_for.
- `category`: exact category match.
- `source_cluster`: exact PRD page-matrix cluster match, e.g. `/rsp-editing-prompts`.
- `provider_tag`: exact provider tag match.
- `home_featured`: `true|false`.
- `index_ready`: `true|false`.
- `limit`: 1–100.

Response shape:

```json
{
  "ok": true,
  "contract": "seo_led_effect_catalog_v1",
  "site": "aieditorrsp.net",
  "version": "effect-catalog-seo-led-v1",
  "counts": {
    "total_effects": 75,
    "index_ready_effects": 75,
    "returned": 3
  },
  "facets": {
    "categories": [],
    "source_clusters": [],
    "provider_tags": [],
    "home_featured_slugs": []
  },
  "editor_state": {
    "provider_enabled": false,
    "provider_name": "fal",
    "model": "fal-ai/flux-pro/kontext",
    "mode": "copy_prompt_only_preview",
    "disabled_reason": "missing_provider_secret",
    "copy_prompt_enabled": true,
    "upload_to_edit_enabled": false
  },
  "guarantees": {
    "copy_prompt_always_available": true,
    "copy_prompt_requires_auth": false,
    "copy_prompt_requires_credits": false,
    "upload_to_edit_honest_provider_state": true
  },
  "effects": []
}
```

### `GET /api/effects/{slug}`

Returns one detail record with:

- `copy_prompt`
- `before_image_prompt`
- `after_image_prompt`
- `variables`
- `negative_prompt_constraints`
- `external_use_hints`
- `editor_deeplink_params`
- `analytics`
- `unofficial_disclaimer`

404 contract:

```json
{ "ok": false, "code": "EFFECT_NOT_FOUND", "message": "Effect not found." }
```

### `GET /api/editor-config`

Runtime truth endpoint for the dual-mode editor.

Response guarantees:

- `copy_prompt.enabled=true` regardless of auth/upload/credits/provider.
- `upload_to_edit.provider_enabled` is true only when provider name is supported, secret exists, and model is approved for image-to-image.
- `upload_to_edit.disabled_reason` is one of:
  - `unsupported_provider`
  - `missing_provider_secret`
  - `model_not_approved_for_image_to_image`
  - `null` when enabled.
- `ui_truth_contract.no_text_to_image_fallback=true`.
- `ui_truth_contract.failed_provider_calls_do_not_charge_credits=true`.

### `GET /api/prompt-templates`

Backward compatible plus new catalog:

- `legacy_templates`: previous `StitchStudio` template set.
- `templates`: SEO-led 75-effect catalog mapped into template-compatible fields.
- `catalog.total_effects=75`.
- `catalog.copy_prompt_always_available=true`.

## Frontend handoff

Use this rule directly:

```ts
const config = await fetch('/api/editor-config').then(r => r.json())

// Always render Copy Prompt from /api/effects or /api/effects/{slug}.
copyPromptButton.disabled = false

// Upload-to-Edit must follow provider truth.
uploadToEditButton.disabled = !config.upload_to_edit.provider_enabled
providerBadge.textContent = config.upload_to_edit.provider_enabled
  ? 'Live upload-to-edit'
  : `Copy prompt mode · ${config.upload_to_edit.disabled_reason}`
```

Effect cards should call:

- list/home grid: `/api/effects?home_featured=true&limit=9`
- library search: `/api/effects?q=cinematic&source_cluster=/rsp-editing-prompts`
- detail: `/api/effects/rsp-editing-ai-boy-cinematic`
- editor state: `/api/editor-config`

Do not gate Copy Prompt on login, upload, credits, checkout, or provider availability.

## Verification

Local OpenNext/Worker build:

```text
npm run build
PASS — OpenNext build complete.
Routes include:
ƒ /api/editor-config
ƒ /api/effects
ƒ /api/effects/[slug]
ƒ /api/prompt-templates
```

Local wrangler smoke:

```text
GET http://127.0.0.1:8791/api/effects?home_featured=true&limit=3
HTTP 200
contract=seo_led_effect_catalog_v1
returned=3
copy_prompt_always_available=true
```

```text
GET http://127.0.0.1:8791/api/editor-config
HTTP 200
contract=dual_mode_editor_state_v1
copy_prompt.enabled=true
upload_to_edit.provider_enabled=false locally
upload_to_edit.disabled_reason=missing_provider_secret locally
catalog.total_effects=75
```

```text
GET http://127.0.0.1:8791/api/effects/rsp-editing-ai-boy-cinematic
HTTP 200
contract=seo_led_effect_detail_v1
copy_prompt_requires_auth=false
copy_prompt_requires_credits=false
```

The local `provider_enabled=false` is expected because local `wrangler dev` did not have provider secrets loaded.

Production deploy:

```text
npm run deploy:raw
PASS — Worker uploaded and deployed.
Current Version ID: f3e85dc4-a1f8-4595-a0ae-b0bad240cf3c
Custom domain: aieditorrsp.net
```

Production smoke after deploy:

```text
GET https://aieditorrsp.net/api/effects?home_featured=true&limit=3
HTTP 200
contract=seo_led_effect_catalog_v1
returned=3
editor_state.provider_enabled=true
```

```text
GET https://aieditorrsp.net/api/editor-config
HTTP 200
contract=dual_mode_editor_state_v1
catalog.total_effects=75
upload_to_edit.provider_enabled=true
```

```text
GET https://aieditorrsp.net/api/effects/rsp-editing-ai-boy-cinematic
HTTP 200
contract=seo_led_effect_detail_v1
```

```text
GET https://aieditorrsp.net/api/prompt-templates
HTTP 200
contract=prompt_templates_with_seo_led_effects_v1
templates=75
catalog.total_effects=75
```

Git:

```text
commit: 89b8f8d feat: add SEO-led effect catalog APIs
pushed: origin/main
```

## Known limits / next owner

- This task intentionally does not create crawlable `/effects/[slug]` frontend pages or add effect URLs to sitemap, because those routes do not exist yet. Frontend/SEO should only add sitemap URLs after real detail pages exist.
- Static catalog is large (~482 KB source JSON). It is acceptable for this first implementation round, but future catalog growth should move to generated chunked data or D1/R2 if bundle size becomes a problem.
