diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index 93e26cea..eaf86fb2 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -12,7 +12,7 @@ None
- ✅ **v1.0 Multi-Provider Support** - Phases 1-6 (shipped 2026-01-11)
- ✅ **v1.1 Improvements** - Phases 7-14 (shipped 2026-01-12)
-- 🚧 **v1.2 Improvements** - Phases 15-21 (in progress)
+- 🚧 **v1.2 Improvements** - Phases 15-22 (in progress)
## Phases
@@ -230,13 +230,17 @@ Plans:
#### Phase 18: API Route Tests
-**Goal**: Add tests for generate, llm, and workflow API routes
+**Goal**: Add tests for generate, llm, models, and workflow API routes
**Depends on**: Phase 17
**Research**: Unlikely (Next.js API route testing patterns)
-**Plans**: TBD
+**Plans**: 5 plans
Plans:
-- [ ] 18-01: TBD (run /gsd:plan-phase 18 to break down)
+- [ ] 18-01: File I/O routes (workflow, save-generation)
+- [ ] 18-02: LLM route (Google, OpenAI providers)
+- [ ] 18-03: Generate route (Gemini provider)
+- [ ] 18-04: Generate route (Replicate, fal.ai providers)
+- [ ] 18-05: Models route (caching, aggregation)
#### Phase 19: Type Refactoring
@@ -274,10 +278,27 @@ Plans:
Plans:
- [ ] 21-01: TBD (run /gsd:plan-phase 21 to break down)
+#### Phase 22: Generate Node Dynamic Input Tests
+
+**Goal**: Test that generate nodes properly validate and render dynamic inputs from provider schemas, and that all inputs/parameters are correctly included in API calls
+**Depends on**: Phase 21
+**Research**: Unlikely (existing test patterns from Phase 17)
+**Plans**: TBD
+
+**Test coverage:**
+1. Dynamic inputs from provider schemas render correctly as parameters and input handles on node creation
+2. Validation of dynamic inputs (required fields, type checking, constraints)
+3. Standard inputs (image, text) validate properly
+4. On submission, all parameters and inputs are included in API call payload correctly
+5. Coverage across all providers (Gemini, Replicate, fal.ai)
+
+Plans:
+- [ ] 22-01: TBD (run /gsd:plan-phase 22 to break down)
+
## Progress
**Execution Order:**
-Phases execute in numeric order: 1 → 2 → ... → 14 → 15 → 16 → 17 → 18 → 19 → 20 → 21
+Phases execute in numeric order: 1 → 2 → ... → 14 → 15 → 16 → 17 → 18 → 19 → 20 → 21 → 22
| Phase | Milestone | Plans Complete | Status | Completed |
|-------|-----------|----------------|--------|-----------|
@@ -302,3 +323,4 @@ Phases execute in numeric order: 1 → 2 → ... → 14 → 15 → 16 → 17 →
| 19. Type Refactoring | v1.2 | 0/? | Not started | - |
| 20. Integration Tests | v1.2 | 0/? | Not started | - |
| 21. Fix Image Input & Deduplication | v1.2 | 0/? | Not started | - |
+| 22. Generate Node Dynamic Input Tests | v1.2 | 0/? | Not started | - |
diff --git a/.planning/STATE.md b/.planning/STATE.md
index 3e1a08e2..5821a1d8 100644
--- a/.planning/STATE.md
+++ b/.planning/STATE.md
@@ -9,10 +9,10 @@ See: .planning/PROJECT.md (updated 2026-01-09)
## Current Position
-Phase: 17 of 21 (Component Tests)
-Plan: 11 of 11 in current phase
-Status: Phase complete
-Last activity: 2026-01-13 - Completed 17-11-PLAN.md (Phase 17 complete!)
+Phase: 18 of 22 (API Route Tests)
+Plan: 0 of 5 in current phase
+Status: Planning complete
+Last activity: 2026-01-13 - Created 5 plans for Phase 18
Progress: █████████░ 91%
@@ -123,10 +123,11 @@ Recent decisions affecting current work:
- Phase 14 added: Fix drag-connect node creation bugs (consolidated from two phases)
- Milestone v1.2 Improvements created: 6 phases (Phase 15-20), testing and modularization
- Phase 21 added: Fix Gemini Pro image input handling and image deduplication issues
+- Phase 22 added: Generate node dynamic input validation and API call tests
## Session Continuity
Last session: 2026-01-13
-Stopped at: Completed Phase 17 (Component Tests)
+Stopped at: Planned Phase 18 (API Route Tests)
Resume file: None
-Next action: Plan Phase 18 (run /gsd:plan-phase 18)
+Next action: Execute Plan 18-01 (run /gsd:execute-plan .planning/phases/18-api-route-tests/18-01-PLAN.md)
diff --git a/.planning/phases/18-api-route-tests/18-01-PLAN.md b/.planning/phases/18-api-route-tests/18-01-PLAN.md
new file mode 100644
index 00000000..991f6e86
--- /dev/null
+++ b/.planning/phases/18-api-route-tests/18-01-PLAN.md
@@ -0,0 +1,122 @@
+---
+phase: 18-api-route-tests
+plan: 01
+type: execute
+---
+
+
+Add tests for workflow and save-generation API routes covering file I/O operations.
+
+Purpose: Establish API route testing patterns with file system mocking for subsequent plans.
+Output: Test files for workflow and save-generation routes with fs mocking patterns.
+
+
+
+~/.claude/get-shit-done/workflows/execute-phase.md
+~/.claude/get-shit-done/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+
+**Routes to test:**
+@src/app/api/workflow/route.ts
+@src/app/api/save-generation/route.ts
+
+**Test patterns reference:**
+@src/store/utils/__tests__/localStorage.test.ts
+@vitest.config.ts
+
+
+
+
+
+ Task 1: Create workflow route tests
+ src/app/api/workflow/__tests__/route.test.ts
+
+Create tests for /api/workflow route covering:
+- POST: Save workflow - validates required fields, creates directories, writes JSON
+- POST: Rejects missing directoryPath/filename/workflow
+- POST: Rejects non-directory paths
+- POST: Handles directory creation for inputs/generations
+- GET: Validates directory existence
+- GET: Returns exists: false for non-existent paths
+
+Mock fs/promises (stat, mkdir, writeFile, readdir) with vi.mock.
+Mock NextRequest/NextResponse using vitest patterns.
+Use vi.spyOn for fs methods to control return values per test.
+
+Pattern for mocking Next.js API routes:
+```typescript
+import { describe, it, expect, vi, beforeEach } from "vitest";
+import { NextRequest } from "next/server";
+import * as fs from "fs/promises";
+
+vi.mock("fs/promises");
+
+// Create mock request helper
+function createMockRequest(body: unknown, method = "POST") {
+ return {
+ json: vi.fn().mockResolvedValue(body),
+ nextUrl: { searchParams: new URLSearchParams() },
+ } as unknown as NextRequest;
+}
+```
+
+Do NOT mock the entire route module - import and call POST/GET directly.
+
+ npm test -- src/app/api/workflow/__tests__/route.test.ts
+ All workflow route tests pass covering POST save and GET validation
+
+
+
+ Task 2: Create save-generation route tests
+ src/app/api/save-generation/__tests__/route.test.ts
+
+Create tests for /api/save-generation route covering:
+- POST: Saves base64 image with hash-based filename
+- POST: Saves base64 video with hash-based filename
+- POST: Deduplicates existing files by hash suffix
+- POST: Rejects missing directoryPath or content
+- POST: Rejects non-directory paths
+- POST: Handles data URL with various MIME types
+- POST: Handles HTTP URLs (mock fetch for external URL)
+
+Mock fs/promises (stat, mkdir, writeFile, readdir) with vi.mock.
+Mock global fetch for HTTP URL handling.
+Use crypto.createHash for consistent hash generation in tests.
+
+Test the deduplication logic:
+1. Mock readdir to return file with matching hash suffix
+2. Verify no writeFile called, isDuplicate: true returned
+
+Test hash generation:
+1. Provide known content, verify hash in filename
+2. Use crypto.createHash('md5').update(buffer).digest('hex') to compute expected hash
+
+ npm test -- src/app/api/save-generation/__tests__/route.test.ts
+ All save-generation route tests pass covering save, deduplication, and validation
+
+
+
+
+
+Before declaring plan complete:
+- [ ] `npm test -- src/app/api/workflow/__tests__/route.test.ts` passes
+- [ ] `npm test -- src/app/api/save-generation/__tests__/route.test.ts` passes
+- [ ] Both test files use consistent mocking patterns
+- [ ] No TypeScript errors in test files
+
+
+
+- All tasks completed
+- All verification checks pass
+- Tests cover validation, success, and error paths
+- Mocking patterns established for fs and fetch
+
+
+
diff --git a/.planning/phases/18-api-route-tests/18-02-PLAN.md b/.planning/phases/18-api-route-tests/18-02-PLAN.md
new file mode 100644
index 00000000..f377f616
--- /dev/null
+++ b/.planning/phases/18-api-route-tests/18-02-PLAN.md
@@ -0,0 +1,140 @@
+---
+phase: 18-api-route-tests
+plan: 02
+type: execute
+---
+
+
+Add tests for LLM API route covering Google and OpenAI provider paths.
+
+Purpose: Test text generation API with provider mocking patterns.
+Output: Comprehensive tests for /api/llm route with mocked external APIs.
+
+
+
+~/.claude/get-shit-done/workflows/execute-phase.md
+~/.claude/get-shit-done/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+
+**Route to test:**
+@src/app/api/llm/route.ts
+
+**Test patterns from previous plan:**
+@src/app/api/workflow/__tests__/route.test.ts
+
+
+
+
+
+ Task 1: Create LLM route tests for Google provider
+ src/app/api/llm/__tests__/route.test.ts
+
+Create tests for /api/llm route Google provider path:
+- POST: Generates text with Google/Gemini successfully
+- POST: Handles multimodal input (images + prompt)
+- POST: Rejects missing prompt
+- POST: Rejects missing API key (no env var, no header)
+- POST: Returns 429 on rate limit errors
+- POST: Returns 500 on API errors
+
+Mock @google/genai GoogleGenAI class:
+```typescript
+vi.mock("@google/genai", () => ({
+ GoogleGenAI: vi.fn().mockImplementation(() => ({
+ models: {
+ generateContent: vi.fn().mockResolvedValue({
+ text: "Generated response text",
+ }),
+ },
+ })),
+}));
+```
+
+Mock process.env.GEMINI_API_KEY for auth tests.
+Test X-Gemini-API-Key header takes precedence over env var.
+
+Test request structure:
+```typescript
+const mockRequest = {
+ json: vi.fn().mockResolvedValue({
+ prompt: "Test prompt",
+ provider: "google",
+ model: "gemini-2.5-flash",
+ temperature: 0.7,
+ maxTokens: 1024,
+ }),
+ headers: new Headers(),
+} as unknown as NextRequest;
+```
+
+ npm test -- src/app/api/llm/__tests__/route.test.ts --testNamePattern="google"
+ Google provider tests pass covering success, validation, and error paths
+
+
+
+ Task 2: Add LLM route tests for OpenAI provider
+ src/app/api/llm/__tests__/route.test.ts
+
+Add tests for /api/llm route OpenAI provider path to existing test file:
+- POST: Generates text with OpenAI successfully
+- POST: Handles vision input (images + prompt)
+- POST: Rejects unknown provider
+- POST: Rejects missing OpenAI API key
+- POST: Returns 429 on rate limit errors
+- POST: Handles OpenAI API error responses
+
+Mock global fetch for OpenAI API calls:
+```typescript
+const mockFetch = vi.fn();
+global.fetch = mockFetch;
+
+// Success response
+mockFetch.mockResolvedValueOnce({
+ ok: true,
+ json: () => Promise.resolve({
+ choices: [{ message: { content: "OpenAI response" } }],
+ }),
+});
+
+// Error response
+mockFetch.mockResolvedValueOnce({
+ ok: false,
+ status: 401,
+ json: () => Promise.resolve({
+ error: { message: "Invalid API key" },
+ }),
+});
+```
+
+Test X-OpenAI-API-Key header takes precedence over env var.
+Test that correct model ID is sent to OpenAI API.
+
+ npm test -- src/app/api/llm/__tests__/route.test.ts
+ All LLM route tests pass covering both providers
+
+
+
+
+
+Before declaring plan complete:
+- [ ] `npm test -- src/app/api/llm/__tests__/route.test.ts` passes
+- [ ] Tests cover Google and OpenAI providers
+- [ ] Error handling tested (rate limits, API errors, missing keys)
+- [ ] No TypeScript errors
+
+
+
+- All tasks completed
+- All verification checks pass
+- Both provider paths tested
+- API mocking patterns work correctly
+
+
+
diff --git a/.planning/phases/18-api-route-tests/18-03-PLAN.md b/.planning/phases/18-api-route-tests/18-03-PLAN.md
new file mode 100644
index 00000000..91f6a4f1
--- /dev/null
+++ b/.planning/phases/18-api-route-tests/18-03-PLAN.md
@@ -0,0 +1,125 @@
+---
+phase: 18-api-route-tests
+plan: 03
+type: execute
+---
+
+
+Add tests for generate API route covering Gemini provider path.
+
+Purpose: Test image generation with Gemini, the default and most common path.
+Output: Tests for /api/generate Gemini path with mocked GoogleGenAI SDK.
+
+
+
+~/.claude/get-shit-done/workflows/execute-phase.md
+~/.claude/get-shit-done/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+
+**Route to test:**
+@src/app/api/generate/route.ts
+
+**Test patterns from previous plans:**
+@src/app/api/llm/__tests__/route.test.ts
+
+
+
+
+
+ Task 1: Create generate route tests for Gemini provider
+ src/app/api/generate/__tests__/route.test.ts
+
+Create tests for /api/generate route Gemini (default) path:
+- POST: Generates image successfully with prompt only
+- POST: Generates image with prompt and input images
+- POST: Applies aspect ratio config
+- POST: Applies resolution config for nano-banana-pro model
+- POST: Applies Google Search tool for nano-banana-pro
+- POST: Rejects missing prompt (when no images/dynamicInputs)
+- POST: Returns 500 when API key missing
+- POST: Returns 429 on rate limit errors
+- POST: Handles no candidates in response
+- POST: Handles text-only response (no image)
+
+Mock @google/genai GoogleGenAI class:
+```typescript
+vi.mock("@google/genai", () => ({
+ GoogleGenAI: vi.fn().mockImplementation(() => ({
+ models: {
+ generateContent: vi.fn().mockResolvedValue({
+ candidates: [{
+ content: {
+ parts: [{
+ inlineData: {
+ mimeType: "image/png",
+ data: "base64ImageData",
+ },
+ }],
+ },
+ }],
+ }),
+ },
+ })),
+}));
+```
+
+Test default model selection (nano-banana-pro when not specified).
+Test X-Gemini-API-Key header takes precedence over env var.
+Test response structure matches GenerateResponse type.
+
+ npm test -- src/app/api/generate/__tests__/route.test.ts --testNamePattern="Gemini"
+ Gemini provider tests pass covering success, config options, and error paths
+
+
+
+ Task 2: Add generate route validation and edge case tests
+ src/app/api/generate/__tests__/route.test.ts
+
+Add validation and edge case tests to existing test file:
+- POST: Accepts request with only images (image-to-image)
+- POST: Accepts request with dynamicInputs containing prompt
+- POST: Accepts request with dynamicInputs containing image frames
+- POST: Handles large response payloads (warns but succeeds)
+- POST: Extracts MIME type from data URL correctly
+- POST: Falls back to image/png for raw base64
+
+Test provider routing:
+- POST: Routes to Gemini when no selectedModel provided
+- POST: Routes to Gemini when selectedModel.provider is "gemini"
+
+Test the getInputMappingFromSchema helper (if exported or through behavior):
+- Verify schema parsing maps generic names to model-specific params
+- Test array detection for multi-image inputs
+
+Do NOT test Replicate/fal paths in this plan - those are in 18-04.
+
+ npm test -- src/app/api/generate/__tests__/route.test.ts
+ All Gemini path tests pass including validation and edge cases
+
+
+
+
+
+Before declaring plan complete:
+- [ ] `npm test -- src/app/api/generate/__tests__/route.test.ts` passes
+- [ ] Tests cover Gemini provider path only (Replicate/fal in next plan)
+- [ ] Config options tested (aspectRatio, resolution, googleSearch)
+- [ ] Error handling tested
+- [ ] No TypeScript errors
+
+
+
+- All tasks completed
+- All verification checks pass
+- Gemini path thoroughly tested
+- Input validation tested
+
+
+
diff --git a/.planning/phases/18-api-route-tests/18-04-PLAN.md b/.planning/phases/18-api-route-tests/18-04-PLAN.md
new file mode 100644
index 00000000..44037934
--- /dev/null
+++ b/.planning/phases/18-api-route-tests/18-04-PLAN.md
@@ -0,0 +1,157 @@
+---
+phase: 18-api-route-tests
+plan: 04
+type: execute
+---
+
+
+Add tests for generate API route covering Replicate and fal.ai provider paths.
+
+Purpose: Complete generate route test coverage with external provider mocking.
+Output: Tests for /api/generate Replicate and fal.ai paths with mocked fetch calls.
+
+
+
+~/.claude/get-shit-done/workflows/execute-phase.md
+~/.claude/get-shit-done/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+
+**Route to test:**
+@src/app/api/generate/route.ts
+
+**Existing tests to extend:**
+@src/app/api/generate/__tests__/route.test.ts
+
+
+
+
+
+ Task 1: Add generate route tests for Replicate provider
+ src/app/api/generate/__tests__/route.test.ts
+
+Add tests for /api/generate route Replicate path to existing test file:
+- POST: Generates image successfully via Replicate
+- POST: Generates video successfully via Replicate
+- POST: Returns 401 when Replicate API key missing
+- POST: Handles rate limit (429) from Replicate
+- POST: Handles prediction failure
+- POST: Handles prediction timeout (5 min max)
+- POST: Polls for prediction completion
+- POST: Returns video URL for large videos (>20MB)
+
+Mock global fetch for Replicate API calls:
+```typescript
+// Model info fetch
+mockFetch.mockResolvedValueOnce({
+ ok: true,
+ json: () => Promise.resolve({
+ latest_version: { id: "version123", openapi_schema: {} },
+ }),
+});
+
+// Create prediction
+mockFetch.mockResolvedValueOnce({
+ ok: true,
+ json: () => Promise.resolve({
+ id: "prediction123",
+ status: "starting",
+ }),
+});
+
+// Poll prediction (succeeded)
+mockFetch.mockResolvedValueOnce({
+ ok: true,
+ json: () => Promise.resolve({
+ id: "prediction123",
+ status: "succeeded",
+ output: ["https://replicate.delivery/output.png"],
+ }),
+});
+
+// Fetch output media
+mockFetch.mockResolvedValueOnce({
+ ok: true,
+ headers: new Headers({ "content-type": "image/png" }),
+ arrayBuffer: () => Promise.resolve(new ArrayBuffer(1024)),
+});
+```
+
+Test X-Replicate-API-Key header is passed correctly.
+Test dynamicInputs are passed to prediction input.
+
+ npm test -- src/app/api/generate/__tests__/route.test.ts --testNamePattern="Replicate"
+ Replicate provider tests pass covering success, polling, and error paths
+
+
+
+ Task 2: Add generate route tests for fal.ai provider
+ src/app/api/generate/__tests__/route.test.ts
+
+Add tests for /api/generate route fal.ai path to existing test file:
+- POST: Generates image successfully via fal.ai (images array response)
+- POST: Generates video successfully via fal.ai (video object response)
+- POST: Works without API key (fal.ai allows unauthenticated)
+- POST: Handles rate limit (429) with and without key
+- POST: Handles various response formats (image, images, video, output)
+- POST: Returns video URL for large videos (>20MB)
+- POST: Filters empty dynamicInputs values
+
+Mock global fetch for fal.ai API calls:
+```typescript
+// Sync API call (fal.run)
+mockFetch.mockResolvedValueOnce({
+ ok: true,
+ json: () => Promise.resolve({
+ images: [{ url: "https://fal.media/output.png" }],
+ }),
+});
+
+// Fetch output media
+mockFetch.mockResolvedValueOnce({
+ ok: true,
+ headers: new Headers({ "content-type": "image/png" }),
+ arrayBuffer: () => Promise.resolve(new ArrayBuffer(1024)),
+});
+
+// Video response format
+mockFetch.mockResolvedValueOnce({
+ ok: true,
+ json: () => Promise.resolve({
+ video: { url: "https://fal.media/output.mp4" },
+ }),
+});
+```
+
+Test X-Fal-API-Key header is passed when provided.
+Test getFalInputMapping schema parsing (via behavior).
+
+ npm test -- src/app/api/generate/__tests__/route.test.ts
+ All generate route tests pass covering all three providers
+
+
+
+
+
+Before declaring plan complete:
+- [ ] `npm test -- src/app/api/generate/__tests__/route.test.ts` passes
+- [ ] Tests cover Replicate and fal.ai provider paths
+- [ ] Polling logic tested for Replicate
+- [ ] Various response formats tested for fal.ai
+- [ ] No TypeScript errors
+
+
+
+- All tasks completed
+- All verification checks pass
+- All three providers thoroughly tested
+- Error handling tested for each provider
+
+
+
diff --git a/.planning/phases/18-api-route-tests/18-05-PLAN.md b/.planning/phases/18-api-route-tests/18-05-PLAN.md
new file mode 100644
index 00000000..986954e0
--- /dev/null
+++ b/.planning/phases/18-api-route-tests/18-05-PLAN.md
@@ -0,0 +1,134 @@
+---
+phase: 18-api-route-tests
+plan: 05
+type: execute
+---
+
+
+Add tests for models API route covering caching and provider aggregation.
+
+Purpose: Test model discovery and caching logic for multi-provider support.
+Output: Tests for /api/models route with mocked provider APIs and cache behavior.
+
+
+
+~/.claude/get-shit-done/workflows/execute-phase.md
+~/.claude/get-shit-done/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+
+**Route to test:**
+@src/app/api/models/route.ts
+
+**Cache module to mock:**
+@src/lib/providers/cache.ts
+
+
+
+
+
+ Task 1: Create models route tests for basic functionality
+ src/app/api/models/__tests__/route.test.ts
+
+Create tests for /api/models route core functionality:
+- GET: Returns models from fal.ai when no Replicate key (fal.ai works without key)
+- GET: Returns models from both providers when both keys present
+- GET: Returns 400 when no providers available (Replicate needs key, fal.ai not tested)
+- GET: Filters by provider query param
+- GET: Filters by capabilities query param
+- GET: Searches by query param
+- GET: Returns cached=true when all from cache
+- GET: Returns cached=false when fresh fetch
+
+Mock @/lib/providers/cache:
+```typescript
+vi.mock("@/lib/providers/cache", () => ({
+ getCachedModels: vi.fn(),
+ setCachedModels: vi.fn(),
+ getCacheKey: vi.fn((provider, search) => `${provider}:${search || ""}`),
+}));
+```
+
+Mock global fetch for provider API calls:
+```typescript
+// Replicate models response
+mockFetch.mockResolvedValueOnce({
+ ok: true,
+ json: () => Promise.resolve({
+ results: [{ owner: "stability-ai", name: "sdxl", description: "SDXL" }],
+ next: null,
+ }),
+});
+
+// fal.ai models response
+mockFetch.mockResolvedValueOnce({
+ ok: true,
+ json: () => Promise.resolve({
+ models: [{
+ endpoint_id: "fal-ai/flux",
+ metadata: { display_name: "Flux", category: "text-to-image", description: "" },
+ }],
+ has_more: false,
+ }),
+});
+```
+
+ npm test -- src/app/api/models/__tests__/route.test.ts --testNamePattern="basic"
+ Basic models route tests pass covering filtering and caching
+
+
+
+ Task 2: Add models route tests for caching and error handling
+ src/app/api/models/__tests__/route.test.ts
+
+Add caching and error tests to existing test file:
+- GET: Returns cached models when available (no fetch)
+- GET: Bypasses cache when refresh=true
+- GET: Client-side filters Replicate models (caches full list, filters on read)
+- GET: Handles partial provider failures gracefully
+- GET: Returns 500 when all providers fail
+- GET: Paginates through Replicate results (max 15 pages)
+- GET: Paginates through fal.ai results (max 15 pages)
+
+Test capability inference for Replicate:
+- Video keywords (video, animate, motion) → text-to-video
+- Image-to-video keywords (img2vid, i2v) → image-to-video
+- Image keywords → text-to-image
+
+Test fal.ai category mapping:
+- Category maps directly to ModelCapability
+- Non-relevant categories are filtered out
+
+Test sorting:
+- Models sorted by provider, then by name
+
+ npm test -- src/app/api/models/__tests__/route.test.ts
+ All models route tests pass covering caching, pagination, and error handling
+
+
+
+
+
+Before declaring plan complete:
+- [ ] `npm test -- src/app/api/models/__tests__/route.test.ts` passes
+- [ ] Cache behavior tested (hit, miss, refresh)
+- [ ] Both providers tested
+- [ ] Filtering and search tested
+- [ ] Error handling tested
+- [ ] No TypeScript errors
+
+
+
+- All tasks completed
+- All verification checks pass
+- Cache logic thoroughly tested
+- Provider aggregation tested
+
+
+