@polymorphism-tech/morph-spec 8.38.0 → 8.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +20 -9
- package/README.md +2 -2
- package/bin/morph-spec.js +60 -43
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +4 -3
- package/framework/MORPH.md +10 -9
- package/framework/agents.json +43 -26
- package/framework/docs/CLI.md +7 -5
- package/framework/evals/rubrics/code-quality.md +4 -4
- package/framework/evals/rubrics/condensed.md +2 -2
- package/framework/hooks/README.md +1 -1
- package/framework/hooks/claude-code/core/telemetry-log.js +39 -8
- package/framework/hooks/claude-code/core/worktree-create.js +22 -6
- package/framework/hooks/shared/git-paths.js +38 -5
- package/framework/mcp/registry.json +58 -66
- package/framework/rules/infrastructure-standards.md +0 -1
- package/framework/rules/morph-workflow.md +9 -4
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +59 -25
- package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
- package/framework/skills/core/morph-eval/SKILL.md +1 -1
- package/framework/skills/core/morph-hotfix/SKILL.md +2 -2
- package/framework/skills/core/morph-implement/SKILL.md +48 -24
- package/framework/skills/core/morph-media/SKILL.md +2 -2
- package/framework/skills/core/morph-plan/SKILL.md +10 -3
- package/framework/skills/core/morph-proposal/SKILL.md +1 -1
- package/framework/skills/core/morph-review/SKILL.md +1 -1
- package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +7 -6
- package/framework/standards/PROVENANCE.json +4 -4
- package/framework/standards/STANDARDS.json +9 -9
- package/framework/standards/ai-agents/context-providers.md +16 -26
- package/framework/standards/ai-agents/durable-workflows-hitl.md +15 -21
- package/framework/standards/ai-agents/modalities-image-gen.md +21 -25
- package/framework/standards/ai-agents/modalities-speech-to-text.md +4 -15
- package/framework/standards/ai-agents/modalities-text-to-speech.md +2 -11
- package/framework/standards/ai-agents/modalities-vision.md +1 -18
- package/framework/standards/ai-agents/prompt-sources.md +0 -4
- package/framework/standards/architecture/code-comments.md +52 -16
- package/framework/standards/backend/dotnet/testing.md +4 -2
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +63 -50
- package/framework/standards/frontend/design-system/ai-image-generation.md +42 -28
- package/framework/standards/frontend/design-system/ai-video-generation.md +24 -9
- package/framework/standards/frontend/nextjs/testing.md +4 -2
- package/framework/standards/integration/mcp/mcp-tools.md +64 -126
- package/framework/templates/code/dotnet/abstractions/result.cs +0 -2
- package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +26 -39
- package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +28 -68
- package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +99 -117
- package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +2 -44
- package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +40 -61
- package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +54 -107
- package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +53 -58
- package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +192 -281
- package/framework/templates/code/dotnet/database/migration.cs +49 -83
- package/framework/templates/code/dotnet/database/vector-entity.cs +2 -46
- package/framework/templates/code/dotnet/extensions/result-extensions.cs +0 -2
- package/framework/templates/code/dotnet/feature/validator.cs.hbs +11 -15
- package/framework/templates/code/dotnet/jobs/agent.cs +3 -51
- package/framework/templates/code/dotnet/jobs/job.cs +2 -97
- package/framework/templates/code/dotnet/test.cs +2 -87
- package/framework/templates/code/typescript/contracts.ts +140 -167
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentApi.cs +1 -48
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs +3 -79
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactoryOptions.cs +1 -70
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpec.cs +1 -47
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpecOptions.cs +2 -99
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/AiKitAssemblyMarker.cs +0 -6
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/ConversationStore.cs +3 -88
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/IConversationStore.cs +1 -41
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/InMemoryConversationStore.cs +0 -21
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/CountingChatClient.cs +1 -25
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalAssertion.cs +2 -63
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheMissException.cs +0 -30
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheOptions.cs +1 -52
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheStamp.cs +3 -78
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalFixture.cs +4 -109
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalMode.cs +0 -22
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalQualityEvaluators.cs +1 -36
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalScenario.cs +2 -57
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalSummary.cs +0 -75
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/OfflineChatClient.cs +2 -59
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/ShortPathResponseCache.cs +4 -111
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaGenerator.cs +0 -34
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaProvider.cs +1 -57
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaArtifact.cs +0 -19
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaDisclosure.cs +2 -74
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaGenerator.cs +2 -33
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaIdempotencyKey.cs +1 -43
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJob.cs +1 -96
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobId.cs +0 -13
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobState.cs +0 -27
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaPricing.cs +0 -33
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaProvenance.cs +0 -31
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaRequest.cs +3 -75
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaResult.cs +0 -42
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaReview.cs +0 -39
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaSeams.cs +1 -55
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaStore.cs +3 -59
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MorphMedia.cs +0 -46
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/GoogleImageProvider.cs +3 -28
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/OpenAIImageProvider.cs +7 -61
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/VeoVideoProvider.cs +2 -37
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/IUsageCostSink.cs +1 -23
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostMiddleware.cs +2 -82
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostRecord.cs +1 -68
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphOpenTelemetryExtensions.cs +1 -62
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphTelemetryOptions.cs +2 -62
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/IProviderClientFactory.cs +1 -25
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelAlias.cs +0 -124
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs +1 -73
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaIngest.cs +1 -66
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaPlan.cs +0 -41
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentFactoryTests.cs +2 -112
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentSpecTests.cs +2 -43
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ConversationStoreTests.cs +5 -110
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/DeltaIngestTests.cs +7 -103
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalAssertionTests.cs +1 -42
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheMissTests.cs +1 -30
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheStampTests.cs +1 -19
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFailuresTests.cs +0 -48
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFixtureTests.cs +1 -29
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalRefreshTests.cs +2 -38
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalWorkspace.cs +1 -14
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/KitEvals.cs +4 -69
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/OfflineChatClientTests.cs +1 -10
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClient.cs +1 -57
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClientTests.cs +0 -26
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeClock.cs +1 -19
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeMediaProvider.cs +0 -32
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeProviderClientFactory.cs +0 -14
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeUsageCostSink.cs +0 -24
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/InMemoryMediaStore.cs +0 -32
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAccessibilityTests.cs +0 -37
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAliasIsNotAnAgentTests.cs +0 -24
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +0 -78
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaGeneratorTests.cs +0 -53
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaIdempotencyKeyTests.cs +0 -44
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaJobStateTests.cs +0 -44
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaReviewTests.cs +0 -19
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaStoreTests.cs +0 -24
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ModelRegistryTests.cs +6 -48
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/SkeletonSmokeTests.cs +1 -13
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/TelemetryTests.cs +2 -71
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/UsageCostMiddlewareTests.cs +5 -100
- package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +39 -43
- package/framework/templates/frontend/nextjs/env.mjs.hbs +29 -32
- package/framework/templates/frontend/nextjs/page.tsx.hbs +21 -22
- package/framework/templates/frontend/nextjs/use-feature.ts.hbs +42 -54
- package/framework/templates/integrations/asaas-client.cs +332 -387
- package/framework/templates/integrations/asaas-webhook.cs +290 -351
- package/framework/templates/integrations/neon-auth-config.cs +9 -105
- package/framework/templates/project/validate.js +97 -120
- package/package.json +2 -2
- package/src/commands/dashboard/dashboard.js +80 -53
- package/src/commands/mcp/mcp-setup.js +37 -9
- package/src/commands/project/doctor.js +12 -6
- package/src/commands/project/init.js +6 -3
- package/src/commands/project/install-plugin-cmd.js +1 -1
- package/src/commands/project/update.js +5 -0
- package/src/commands/project/worktree.js +60 -4
- package/src/commands/state/gate-check.js +5 -0
- package/src/commands/tasks/dag.js +27 -4
- package/src/commands/validation/verify.js +22 -38
- package/src/dashboard/app/App.jsx +80 -16
- package/src/dashboard/app/components/ActivityFeed.jsx +172 -0
- package/src/dashboard/app/components/CommandPalette.jsx +155 -0
- package/src/dashboard/app/components/DetailPanel.jsx +47 -11
- package/src/dashboard/app/components/EntityLink.jsx +55 -0
- package/src/dashboard/app/components/ErrorBoundary.jsx +54 -0
- package/src/dashboard/app/components/FlowGraph.jsx +19 -17
- package/src/dashboard/app/components/KnowledgeMarkdown.jsx +53 -0
- package/src/dashboard/app/components/Markdown.jsx +47 -0
- package/src/dashboard/app/components/PhaseRail.jsx +111 -0
- package/src/dashboard/app/components/SearchFilter.jsx +40 -22
- package/src/dashboard/app/components/Sidebar.jsx +146 -37
- package/src/dashboard/app/hooks/useMorphQuery.js +54 -0
- package/src/dashboard/app/hooks/usePref.js +38 -0
- package/src/dashboard/app/lib/format.js +69 -0
- package/src/dashboard/app/lib/phases.js +56 -0
- package/src/dashboard/app/lib/project.js +50 -0
- package/src/dashboard/app/pages/Activity.jsx +24 -0
- package/src/dashboard/app/pages/AgentMap.jsx +371 -370
- package/src/dashboard/app/pages/FeatureDetail.jsx +604 -0
- package/src/dashboard/app/pages/Features.jsx +400 -0
- package/src/dashboard/app/pages/Guide.jsx +146 -0
- package/src/dashboard/app/pages/HookInspector.jsx +357 -322
- package/src/dashboard/app/pages/PhasePipeline.jsx +277 -408
- package/src/dashboard/app/pages/Portfolio.jsx +156 -0
- package/src/dashboard/app/pages/Rubrics.jsx +101 -0
- package/src/dashboard/app/pages/RulesMap.jsx +300 -273
- package/src/dashboard/app/pages/SkillsLibrary.jsx +483 -366
- package/src/dashboard/app/pages/Standards.jsx +407 -332
- package/src/dashboard/app/styles/index.css +187 -47
- package/src/dashboard/index.html +14 -12
- package/src/dashboard/package.json +32 -24
- package/src/dashboard/scanner/activity-scanner.js +351 -0
- package/src/dashboard/scanner/agents-scanner.js +59 -1
- package/src/dashboard/scanner/docs-scanner.js +59 -0
- package/src/dashboard/scanner/feature-status.js +10 -0
- package/src/dashboard/scanner/features-scanner.js +360 -0
- package/src/dashboard/scanner/index.js +14 -7
- package/src/dashboard/scanner/phases-scanner.js +14 -15
- package/src/dashboard/scanner/portfolio.js +153 -0
- package/src/dashboard/scanner/project-api.js +195 -0
- package/src/dashboard/scanner/rules-scanner.js +42 -37
- package/src/dashboard/scanner/skills-scanner.js +105 -43
- package/src/dashboard/scanner/standards-scanner.js +2 -0
- package/src/dashboard/vite-plugin-morph-data.js +134 -17
- package/src/lib/detectors/claude-config-detector.js +2 -7
- package/src/lib/detectors/index.js +5 -1
- package/src/lib/evaluator-report.js +22 -0
- package/src/lib/generators/settings-generator.js +208 -209
- package/src/lib/installers/mcp-installer.js +724 -719
- package/src/lib/installers/memory-installer.js +1 -2
- package/src/lib/projects/registry.js +154 -0
- package/src/lib/risk-detector.js +13 -0
- package/src/lib/tasks/tasks-json-parser.js +21 -0
- package/src/lib/validators/code-budget/code-budget-validator.js +32 -4
- package/src/lib/worktree/env-allocator.js +9 -3
- package/src/scripts/install-plugin.js +1 -1
- package/src/scripts/setup-infra.js +2 -4
- package/src/utils/agents-installer.js +108 -13
- package/src/utils/banner.js +1 -1
- package/src/commands/index.js +0 -13
- package/src/commands/project/checkpoint.js +0 -209
- package/src/core/state/phase-state-machine.js +0 -197
- package/src/dashboard/app/hooks/useDetailPanel.js +0 -15
- package/src/dashboard/app/pages/LiveState.jsx +0 -206
- package/src/dashboard/scanner/squads-scanner.js +0 -13
- package/src/dashboard/scanner/state-scanner.js +0 -85
- package/src/lib/generators/index.js +0 -10
- package/src/lib/hooks/hook-executor.js +0 -167
- package/src/lib/validators/checkpoint-persistence.js +0 -39
- package/src/lib/validators/shared/emit-validator-dispatch.js +0 -14
- package/src/lib/validators/shared/index.js +0 -12
- package/src/lib/validators/shared/issue-counter.js +0 -14
- package/src/lib/validators/shared/result-formatter.js +0 -124
|
@@ -125,16 +125,19 @@ real sign-in attempt, not after debugging a mysterious "session never sticks" bu
|
|
|
125
125
|
|
|
126
126
|
### SDK Initialization
|
|
127
127
|
|
|
128
|
+
`lib/auth/client.ts` — `createAuthClient` takes the public auth URL as a positional argument, and the
|
|
129
|
+
React hooks are re-exported from the client instance for ergonomic imports:
|
|
130
|
+
|
|
128
131
|
```typescript
|
|
129
|
-
// lib/auth/client.ts
|
|
130
132
|
'use client';
|
|
131
133
|
import { createAuthClient } from '@neondatabase/auth/next';
|
|
132
|
-
// createAuthClient takes the public auth URL as a positional argument
|
|
133
134
|
export const authClient = createAuthClient(process.env.NEXT_PUBLIC_AUTH_URL!);
|
|
134
|
-
// Re-export the React hooks from the client instance for ergonomic imports
|
|
135
135
|
export const { useSession } = authClient;
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`lib/auth/server.ts`:
|
|
136
139
|
|
|
137
|
-
|
|
140
|
+
```typescript
|
|
138
141
|
import { createNeonAuth } from '@neondatabase/auth/next/server';
|
|
139
142
|
export const auth = createNeonAuth({
|
|
140
143
|
baseUrl: process.env.NEON_AUTH_BASE_URL!,
|
|
@@ -152,8 +155,9 @@ Import the Tailwind styles for the UI components in `globals.css`:
|
|
|
152
155
|
@import '@neondatabase/auth-ui/tailwind';
|
|
153
156
|
```
|
|
154
157
|
|
|
158
|
+
`app/providers.tsx`:
|
|
159
|
+
|
|
155
160
|
```tsx
|
|
156
|
-
// app/providers.tsx
|
|
157
161
|
'use client';
|
|
158
162
|
import { NeonAuthUIProvider } from '@neondatabase/auth-ui';
|
|
159
163
|
import { authClient } from '@/lib/auth/client';
|
|
@@ -169,8 +173,9 @@ export function Providers({ children }: { children: React.ReactNode }) {
|
|
|
169
173
|
|
|
170
174
|
### Auth API Route
|
|
171
175
|
|
|
176
|
+
`app/api/auth/[...path]/route.ts`:
|
|
177
|
+
|
|
172
178
|
```typescript
|
|
173
|
-
// app/api/auth/[...path]/route.ts
|
|
174
179
|
import { auth } from '@/lib/auth/server';
|
|
175
180
|
|
|
176
181
|
export const { GET, POST } = auth.handler();
|
|
@@ -182,8 +187,9 @@ export const { GET, POST } = auth.handler();
|
|
|
182
187
|
|
|
183
188
|
### Sign In / Sign Up Pages
|
|
184
189
|
|
|
190
|
+
`app/auth/sign-in/page.tsx`:
|
|
191
|
+
|
|
185
192
|
```tsx
|
|
186
|
-
// app/auth/sign-in/page.tsx
|
|
187
193
|
import { AuthView } from '@neondatabase/auth-ui';
|
|
188
194
|
|
|
189
195
|
export default function SignInPage() {
|
|
@@ -193,8 +199,11 @@ export default function SignInPage() {
|
|
|
193
199
|
</div>
|
|
194
200
|
);
|
|
195
201
|
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
`app/auth/sign-up/page.tsx`:
|
|
196
205
|
|
|
197
|
-
|
|
206
|
+
```tsx
|
|
198
207
|
import { AuthView } from '@neondatabase/auth-ui';
|
|
199
208
|
|
|
200
209
|
export default function SignUpPage() {
|
|
@@ -208,8 +217,9 @@ export default function SignUpPage() {
|
|
|
208
217
|
|
|
209
218
|
### Server-Side Auth Check
|
|
210
219
|
|
|
220
|
+
`app/dashboard/page.tsx`:
|
|
221
|
+
|
|
211
222
|
```typescript
|
|
212
|
-
// app/dashboard/page.tsx
|
|
213
223
|
import { auth } from '@/lib/auth/server';
|
|
214
224
|
import { redirect } from 'next/navigation';
|
|
215
225
|
|
|
@@ -228,8 +238,9 @@ export default async function DashboardPage() {
|
|
|
228
238
|
|
|
229
239
|
### Client Component with User Data
|
|
230
240
|
|
|
241
|
+
`components/UserProfile.tsx`:
|
|
242
|
+
|
|
231
243
|
```tsx
|
|
232
|
-
// components/UserProfile.tsx
|
|
233
244
|
'use client';
|
|
234
245
|
import { useSession } from '@/lib/auth/client';
|
|
235
246
|
|
|
@@ -249,14 +260,15 @@ export function UserProfile() {
|
|
|
249
260
|
|
|
250
261
|
### Route Protection (Middleware)
|
|
251
262
|
|
|
263
|
+
`middleware.ts`. The `runtime = 'nodejs'` line is **mandatory** for any `middleware.ts` that touches
|
|
264
|
+
Neon Auth: Next.js middleware runs on the Edge runtime by default, and the network call the Neon Auth
|
|
265
|
+
SDK makes to mint the session-data cookie times out inside the Edge sandbox on `next dev` — the request
|
|
266
|
+
silently bounces back to sign-in even with a fully valid session, with no error surfaced.
|
|
267
|
+
|
|
252
268
|
```typescript
|
|
253
|
-
// middleware.ts
|
|
254
269
|
import { auth } from '@/lib/auth/server';
|
|
255
270
|
|
|
256
|
-
//
|
|
257
|
-
// runtime by default, and the network call the Neon Auth SDK makes to mint the session-data cookie
|
|
258
|
-
// times out inside the Edge sandbox on `next dev` — the request silently bounces back to sign-in
|
|
259
|
-
// even with a fully valid session, with no error surfaced. Force the Node.js runtime instead.
|
|
271
|
+
// Mandatory: on the Edge runtime the SDK's session-cookie call times out and silently bounces to sign-in.
|
|
260
272
|
export const runtime = 'nodejs';
|
|
261
273
|
|
|
262
274
|
export default auth.middleware({
|
|
@@ -294,7 +306,6 @@ export default function ProtectedPage() {
|
|
|
294
306
|
## OAuth (Social Login)
|
|
295
307
|
|
|
296
308
|
```typescript
|
|
297
|
-
// Trigger OAuth sign-in
|
|
298
309
|
await authClient.signIn.social({
|
|
299
310
|
provider: 'google',
|
|
300
311
|
callbackURL: 'http://localhost:3000/auth/callback',
|
|
@@ -307,8 +318,9 @@ Configure OAuth providers in the Neon Console under Auth > Configuration.
|
|
|
307
318
|
|
|
308
319
|
## API Route Protection
|
|
309
320
|
|
|
321
|
+
`app/api/protected/route.ts`:
|
|
322
|
+
|
|
310
323
|
```typescript
|
|
311
|
-
// app/api/protected/route.ts
|
|
312
324
|
import { auth } from '@/lib/auth/server';
|
|
313
325
|
|
|
314
326
|
export const dynamic = 'force-dynamic';
|
|
@@ -348,34 +360,38 @@ backend's real prefix and the proxy's public prefix don't happen to match charac
|
|
|
348
360
|
because this is a runtime routing mismatch (404, or the proxy calling itself), it **never shows up at
|
|
349
361
|
build time**, only when a human clicks the page.
|
|
350
362
|
|
|
363
|
+
`lib/api/prefixes.ts` — `BACKEND_API_PREFIX` is used ONLY by Server Components / Route Handlers
|
|
364
|
+
calling the .NET backend directly (e.g. `https://api.example.com`; the backend's own routes have NO
|
|
365
|
+
`/api` prefix); `PROXY_API_PREFIX` is used ONLY by Client Components calling the same-origin Next.js
|
|
366
|
+
proxy, and matches `app/api/proxy/[...path]/route.ts`:
|
|
367
|
+
|
|
351
368
|
```typescript
|
|
352
|
-
|
|
353
|
-
// BACKEND_API_PREFIX: used ONLY by Server Components / Route Handlers calling the .NET backend directly.
|
|
354
|
-
export const BACKEND_API_PREFIX = process.env.API_URL; // e.g. https://api.example.com — backend's own routes have NO /api prefix
|
|
369
|
+
export const BACKEND_API_PREFIX = process.env.API_URL;
|
|
355
370
|
|
|
356
|
-
|
|
357
|
-
export const PROXY_API_PREFIX = '/api/proxy'; // matches app/api/proxy/[...path]/route.ts
|
|
371
|
+
export const PROXY_API_PREFIX = '/api/proxy';
|
|
358
372
|
```
|
|
359
373
|
|
|
374
|
+
The two call sites below — `app/(dashboard)/catalog/page.tsx`, a Server Component calling the backend
|
|
375
|
+
directly, and `features/catalog/hooks/use-items.ts`, a Client Component going through the same-origin
|
|
376
|
+
proxy — MUST resolve to the same backend route:
|
|
377
|
+
|
|
360
378
|
```typescript
|
|
361
|
-
// app/(dashboard)/catalog/page.tsx — Server Component, direct backend call
|
|
362
379
|
import { BACKEND_API_PREFIX } from '@/lib/api/prefixes';
|
|
363
380
|
|
|
364
381
|
async function getItems() {
|
|
365
|
-
// cross-reference: PROXY_API_PREFIX below MUST resolve to the same backend route
|
|
366
382
|
const res = await fetch(`${BACKEND_API_PREFIX}/catalog/items`, { next: { revalidate: 60 } });
|
|
367
383
|
return res.json();
|
|
368
384
|
}
|
|
369
385
|
```
|
|
370
386
|
|
|
387
|
+
`features/catalog/hooks/use-items.ts`:
|
|
388
|
+
|
|
371
389
|
```typescript
|
|
372
|
-
// features/catalog/hooks/use-items.ts — Client Component, via same-origin proxy
|
|
373
390
|
import { PROXY_API_PREFIX } from '@/lib/api/prefixes';
|
|
374
391
|
|
|
375
392
|
export function useItems() {
|
|
376
393
|
return useQuery({
|
|
377
394
|
queryKey: ['catalog', 'items'],
|
|
378
|
-
// cross-reference: BACKEND_API_PREFIX above MUST resolve to the same backend route
|
|
379
395
|
queryFn: async () => (await fetch(`${PROXY_API_PREFIX}/catalog/items`)).json(),
|
|
380
396
|
});
|
|
381
397
|
}
|
|
@@ -435,31 +451,30 @@ Before marking any task that adds or touches an endpoint as done:
|
|
|
435
451
|
|
|
436
452
|
### JWT Bearer Configuration
|
|
437
453
|
|
|
454
|
+
`Infrastructure/Auth/NeonAuthOptions.cs` — `BaseUrl` is the Neon Auth Base URL, e.g.
|
|
455
|
+
`https://ep-xxx.neonauth.us-east-2.aws.neon.build/neondb/auth`:
|
|
456
|
+
|
|
438
457
|
```csharp
|
|
439
|
-
// Infrastructure/Auth/NeonAuthOptions.cs
|
|
440
458
|
namespace {ProjectName}.Infrastructure.Auth;
|
|
441
459
|
|
|
442
460
|
public sealed class NeonAuthOptions
|
|
443
461
|
{
|
|
444
462
|
public const string SectionName = "NeonAuth";
|
|
445
463
|
|
|
446
|
-
/// <summary>Neon Auth Base URL, e.g. https://ep-xxx.neonauth.us-east-2.aws.neon.build/neondb/auth</summary>
|
|
447
464
|
public string BaseUrl { get; set; } = string.Empty;
|
|
448
465
|
}
|
|
449
466
|
```
|
|
450
467
|
|
|
468
|
+
`Infrastructure/Auth/NeonAuthJwksProvider.cs` fetches and caches Neon Auth's raw JWKS
|
|
469
|
+
(`/.well-known/jwks.json` — there is no OIDC discovery document) for 15 minutes; an unknown `kid`
|
|
470
|
+
forces one refresh, to survive key rotation without waiting out the TTL:
|
|
471
|
+
|
|
451
472
|
```csharp
|
|
452
|
-
// Infrastructure/Auth/NeonAuthJwksProvider.cs
|
|
453
473
|
namespace {ProjectName}.Infrastructure.Auth;
|
|
454
474
|
|
|
455
475
|
using Microsoft.Extensions.Options;
|
|
456
476
|
using Microsoft.IdentityModel.Tokens;
|
|
457
477
|
|
|
458
|
-
/// <summary>
|
|
459
|
-
/// Fetches and caches Neon Auth's raw JWKS (/.well-known/jwks.json — there is no OIDC discovery
|
|
460
|
-
/// document). Cached for 15 minutes; a forced refresh happens once when an unknown `kid` is seen,
|
|
461
|
-
/// to survive key rotation without waiting out the TTL.
|
|
462
|
-
/// </summary>
|
|
463
478
|
public sealed class NeonAuthJwksProvider(HttpClient httpClient, IOptions<NeonAuthOptions> options)
|
|
464
479
|
{
|
|
465
480
|
private static readonly TimeSpan CacheTtl = TimeSpan.FromMinutes(15);
|
|
@@ -473,7 +488,7 @@ public sealed class NeonAuthJwksProvider(HttpClient httpClient, IOptions<NeonAut
|
|
|
473
488
|
var key = keySet.Keys.FirstOrDefault(k => k.Kid == kid);
|
|
474
489
|
if (key is not null) return key;
|
|
475
490
|
|
|
476
|
-
// Unknown kid
|
|
491
|
+
// Unknown kid may be key rotation: refresh once before giving up.
|
|
477
492
|
keySet = await GetKeySetAsync(forceRefresh: true, cancellationToken);
|
|
478
493
|
return keySet.Keys.FirstOrDefault(k => k.Kid == kid);
|
|
479
494
|
}
|
|
@@ -503,8 +518,9 @@ public sealed class NeonAuthJwksProvider(HttpClient httpClient, IOptions<NeonAut
|
|
|
503
518
|
}
|
|
504
519
|
```
|
|
505
520
|
|
|
521
|
+
`Infrastructure/Auth/NeonAuthServiceExtensions.cs`:
|
|
522
|
+
|
|
506
523
|
```csharp
|
|
507
|
-
// Infrastructure/Auth/NeonAuthServiceExtensions.cs
|
|
508
524
|
namespace {ProjectName}.Infrastructure.Auth;
|
|
509
525
|
|
|
510
526
|
using System.Text;
|
|
@@ -527,9 +543,7 @@ public static class NeonAuthServiceExtensions
|
|
|
527
543
|
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
|
528
544
|
.AddJwtBearer(options =>
|
|
529
545
|
{
|
|
530
|
-
//
|
|
531
|
-
// so Authority-driven metadata refresh has nothing valid to fetch. ValidIssuer is set
|
|
532
|
-
// explicitly below instead.
|
|
546
|
+
// No options.Authority: Neon Auth serves no OIDC discovery document, so ValidIssuer is explicit.
|
|
533
547
|
options.TokenValidationParameters = new TokenValidationParameters
|
|
534
548
|
{
|
|
535
549
|
ValidateIssuer = true,
|
|
@@ -537,14 +551,12 @@ public static class NeonAuthServiceExtensions
|
|
|
537
551
|
ValidateAudience = false,
|
|
538
552
|
ValidateLifetime = true,
|
|
539
553
|
NameClaimType = "sub",
|
|
540
|
-
//
|
|
541
|
-
// below (fetched from our own cached JWKS) — this flag doesn't gate that path.
|
|
554
|
+
// Key trust is established inside SignatureValidator below; this flag doesn't gate that path.
|
|
542
555
|
ValidateIssuerSigningKey = false,
|
|
543
556
|
};
|
|
544
557
|
});
|
|
545
558
|
|
|
546
|
-
//
|
|
547
|
-
// so this only *adds* the SignatureValidator to the TokenValidationParameters built above.
|
|
559
|
+
// Runs after AddJwtBearer's own setup, with DI available: it only adds the SignatureValidator.
|
|
548
560
|
services.AddOptions<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme)
|
|
549
561
|
.Configure<NeonAuthJwksProvider>((options, jwks) =>
|
|
550
562
|
{
|
|
@@ -561,9 +573,7 @@ public static class NeonAuthServiceExtensions
|
|
|
561
573
|
if (string.IsNullOrEmpty(kid))
|
|
562
574
|
throw new SecurityTokenInvalidSignatureException("Token is missing 'kid'.");
|
|
563
575
|
|
|
564
|
-
// Blocking
|
|
565
|
-
// I/O-free on the hot path. SignatureValidator has no async overload in this
|
|
566
|
-
// library version — only a cold-start / key-rotation miss ever awaits real I/O here.
|
|
576
|
+
// Blocking is safe: SignatureValidator has no async overload and the JWKS is cached in memory.
|
|
567
577
|
var jwk = jwks.GetKeyAsync(kid, CancellationToken.None).GetAwaiter().GetResult()
|
|
568
578
|
?? throw new SecurityTokenInvalidSignatureException($"No JWKS key found for kid '{kid}'.");
|
|
569
579
|
|
|
@@ -585,10 +595,7 @@ public static class NeonAuthServiceExtensions
|
|
|
585
595
|
if (!verifier.VerifySignature(signature))
|
|
586
596
|
throw new SecurityTokenInvalidSignatureException("Ed25519 signature verification failed.");
|
|
587
597
|
|
|
588
|
-
//
|
|
589
|
-
// uses JsonWebTokenHandler internally. Returning a
|
|
590
|
-
// System.IdentityModel.Tokens.Jwt.JwtSecurityToken here fails with IDX10506
|
|
591
|
-
// ("SignatureValidator returned a token of an unexpected type").
|
|
598
|
+
// Must be JsonWebToken: ASP.NET Core 8+ rejects a JwtSecurityToken here with IDX10506.
|
|
592
599
|
return jwt;
|
|
593
600
|
};
|
|
594
601
|
});
|
|
@@ -600,6 +607,12 @@ public static class NeonAuthServiceExtensions
|
|
|
600
607
|
}
|
|
601
608
|
```
|
|
602
609
|
|
|
610
|
+
Two details of the validator above: it returns `Microsoft.IdentityModel.JsonWebTokens.JsonWebToken`
|
|
611
|
+
because ASP.NET Core 8+ uses `JsonWebTokenHandler` internally — returning a
|
|
612
|
+
`System.IdentityModel.Tokens.Jwt.JwtSecurityToken` fails with IDX10506 ("SignatureValidator returned a
|
|
613
|
+
token of an unexpected type"); and the blocking `GetResult()` is I/O-free on the hot path, since only a
|
|
614
|
+
cold-start or key-rotation miss ever awaits real I/O.
|
|
615
|
+
|
|
603
616
|
### Protected Endpoints
|
|
604
617
|
|
|
605
618
|
```csharp
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
> **Scope:** nextjs
|
|
4
4
|
> **Layer:** 1 (load when relevant)
|
|
5
|
-
> **Keywords:** image, gemini, asset, ai-generation, placeholder, mcp, next/image
|
|
5
|
+
> **Keywords:** image, gpt-image, gemini, openrouter, morph-assets, asset, ai-generation, placeholder, mcp, next/image
|
|
6
6
|
> **Load When:** feature has UI components requiring image assets
|
|
7
7
|
|
|
8
|
-
**Verified against:**
|
|
8
|
+
**Verified against:** morph-assets MCP (OpenRouter; default model `openai/gpt-image-2.5-flare`) + Next.js 15 `next/image`. Last-verified: 2026-09-27.
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
| Use Case | Approach | Examples |
|
|
15
15
|
|----------|----------|---------|
|
|
16
|
-
| **AI generation** |
|
|
16
|
+
| **AI generation** | morph-assets MCP | Hero images, brand illustrations, feature-specific visuals, empty states, background panels |
|
|
17
17
|
| **Stock photos** | External sources | Generic content (people in offices, nature scenes, stock portraits) |
|
|
18
18
|
| **SVG/CSS** | Code-based | Icons, patterns, gradients, geometric shapes, loading animations |
|
|
19
19
|
| **Skip** | Other tools | Screenshots (Playwright), diagrams (Mermaid), logos (vector tools) |
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
## Brand Fidelity: Reference, Don't Invent
|
|
24
24
|
|
|
25
|
-
The advantage of an image MCP over generating from scratch is staying consistent with the client's **real** assets. Pass them as reference (`edit_image` with `
|
|
25
|
+
The advantage of an image MCP over generating from scratch is staying consistent with the client's **real** assets. Pass them as reference (`edit_image` / `references`, or `@brand` with a `project`) rather than generating in a vacuum. A generated look-alike is a different product, and the client sees it immediately.
|
|
26
26
|
|
|
27
27
|
Two cases where this is a hard rule:
|
|
28
28
|
|
|
@@ -34,46 +34,60 @@ Two cases where this is a hard rule:
|
|
|
34
34
|
Generate the **hero first**. Once approved, use it as the reference for every derived image (detail shots, cards, scene variants), so the whole set shares light, palette and identity instead of looking like separate photo shoots.
|
|
35
35
|
|
|
36
36
|
```
|
|
37
|
-
hero -> [approved] -> derivatives with the approved hero as
|
|
37
|
+
hero -> [approved] -> derivatives with the approved hero as references -> [approved per image]
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
Skipping this chain is the most common cause of a page whose images do not feel like one set.
|
|
41
41
|
|
|
42
|
-
##
|
|
42
|
+
## morph-assets MCP API
|
|
43
|
+
|
|
44
|
+
One MCP for image and video, all models through OpenRouter. `model` accepts any OpenRouter image model by id, alias (`flare`, `mai flash`, `nano banana 2`, `nano banana pro`) or approximate name.
|
|
45
|
+
|
|
46
|
+
| Role | Model |
|
|
47
|
+
|------|-------|
|
|
48
|
+
| Default (editing with refs, text) | `openai/gpt-image-2.5-flare` |
|
|
49
|
+
| Draft | `microsoft/mai-image-2.6-flash` (max 5 refs) |
|
|
50
|
+
| 2K/4K output or 6-14 references | `google/gemini-3.1-flash-image` |
|
|
43
51
|
|
|
44
52
|
```javascript
|
|
45
53
|
// Generate a new image
|
|
46
|
-
|
|
47
|
-
prompt: string, //
|
|
54
|
+
mcp__morph-assets__generate_image({
|
|
55
|
+
prompt: string, // Narrative description; copy to render goes between double quotes
|
|
56
|
+
model?: string, // Default: openai/gpt-image-2.5-flare
|
|
57
|
+
models?: string[], // 2-4 models: same request in each, cost per model (to choose)
|
|
48
58
|
aspectRatio?: "1:1" | "2:3" | "3:2" | "3:4" | "4:3" | "9:16" | "16:9",
|
|
59
|
+
resolution?: "1K" | "2K" | "4K", // Only on models that support it
|
|
60
|
+
variants?: number, // 1-4, returns a contact sheet for the review
|
|
61
|
+
references?: string[], // Paths, URLs, library ids, "@brand", "@brand:name"
|
|
62
|
+
project?: string, // Client project: output dir, brand refs, budget
|
|
49
63
|
outputPath?: string, // File path to save (e.g., "public/images/feature/hero.png")
|
|
50
|
-
|
|
64
|
+
dryRun?: boolean, // Estimate cost only
|
|
65
|
+
confirmToken?: string, // Repeat a CONFIRMATION_REQUIRED call after approval
|
|
51
66
|
})
|
|
52
67
|
|
|
53
|
-
// Edit an existing image
|
|
54
|
-
|
|
55
|
-
|
|
68
|
+
// Edit an existing image (the base image goes as the 1st reference)
|
|
69
|
+
mcp__morph-assets__edit_image({
|
|
70
|
+
image: string, // Path to image or library id
|
|
56
71
|
prompt: string, // Short, targeted edit instructions (1-2 sentences)
|
|
72
|
+
references?: string[],
|
|
57
73
|
outputPath?: string,
|
|
58
74
|
})
|
|
59
75
|
|
|
60
|
-
// Continue editing last image
|
|
61
|
-
|
|
76
|
+
// Continue editing the last image of the project
|
|
77
|
+
mcp__morph-assets__continue_editing({
|
|
62
78
|
prompt: string, // One adjustment at a time
|
|
79
|
+
imageId?: string, // Default: the last image
|
|
63
80
|
})
|
|
64
81
|
|
|
65
|
-
//
|
|
66
|
-
|
|
67
|
-
type?: "generated" | "edited" | "all",
|
|
68
|
-
limit?: number,
|
|
69
|
-
})
|
|
82
|
+
// History and metadata (replaces list_images / get_image_info)
|
|
83
|
+
mcp__morph-assets__library({ op: "list" | "search" | "info" | "tag" | "project_get" | "project_set", ... })
|
|
70
84
|
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
imageId?: string, // Defaults to last image
|
|
74
|
-
})
|
|
85
|
+
// Pick a model from the curation (verified vs broken)
|
|
86
|
+
mcp__morph-assets__list_models({ kind: "image", needs?: "refs>=5", task?: string })
|
|
75
87
|
```
|
|
76
88
|
|
|
89
|
+
**Cost gate:** every call estimates cost first. Above US$2, over the project budget or with an unknown price, the tool returns `CONFIRMATION_REQUIRED` + `confirmToken`; get human approval and repeat the same call with the token. Use `dryRun: true` before large batches or 4K.
|
|
90
|
+
|
|
77
91
|
## Prompt Engineering Patterns
|
|
78
92
|
|
|
79
93
|
### Design-System-Aware Prompts
|
|
@@ -101,7 +115,7 @@ Describe what the image will BE in the UI, not just the subject:
|
|
|
101
115
|
|
|
102
116
|
### Narrative Style
|
|
103
117
|
|
|
104
|
-
Gemini
|
|
118
|
+
GPT Image, Gemini and MAI all respond best to coherent paragraphs. Build prompts by layering:
|
|
105
119
|
|
|
106
120
|
1. **Subject** — hyper-specific description
|
|
107
121
|
2. **Environment** — foreground, mid-ground, background (layered for depth)
|
|
@@ -148,9 +162,9 @@ One change at a time:
|
|
|
148
162
|
- "More serious expression"
|
|
149
163
|
|
|
150
164
|
### Text Rendering
|
|
151
|
-
|
|
165
|
+
Put the exact copy between double quotes in the prompt (`the headline "Summer Sale"`) so the model renders it literally; GPT Image (the default) is the strongest at text. Models can still misspell words. After generating any image with text:
|
|
152
166
|
1. View the result
|
|
153
|
-
2. If text is wrong: `continue_editing` with "Fix the text — it should read
|
|
167
|
+
2. If text is wrong: `continue_editing` with "Fix the text — it should read "{correct text}" exactly. Keep everything else."
|
|
154
168
|
3. Expect 2-3 iterations for correct text
|
|
155
169
|
|
|
156
170
|
## Aspect Ratio Guide
|
|
@@ -267,7 +281,7 @@ For a frame that will drive a scroll scrub, also generate at the exact ratio the
|
|
|
267
281
|
|
|
268
282
|
## Variations and Cost
|
|
269
283
|
|
|
270
|
-
Generate **two or three variations per image
|
|
284
|
+
Generate **two or three variations per image** (`variants: 2-3` returns a contact sheet for the review), varying angle, light or composition. Seed is not supported by the GPT Image / Gemini models, so variation has to come from the prompt; near-identical options waste the review. To choose between models or directions, `models: [...]` runs the same request on 2-4 models with the cost of each.
|
|
271
285
|
|
|
272
286
|
If none are good, regenerate at the same step with the prompt adjusted by the feedback. **Do not advance with a weak image**: every derivative, any footage built from it, and the final page all inherit its quality.
|
|
273
287
|
|
|
@@ -282,7 +296,7 @@ If none are good, regenerate at the same step with the prompt adjusted by the fe
|
|
|
282
296
|
- [ ] `priority` prop set for above-the-fold images
|
|
283
297
|
- [ ] Build compiles after integration (`npm run build`)
|
|
284
298
|
- [ ] Image visible in dev server at correct route
|
|
285
|
-
- [ ] Client assets passed as `
|
|
299
|
+
- [ ] Client assets passed as `references` (or `@brand`); nothing generated in a vacuum
|
|
286
300
|
- [ ] Real renders and real product photos used as-is, never AI-recreated
|
|
287
301
|
- [ ] Hero approved first, then used as reference for derivatives
|
|
288
302
|
- [ ] Images headed for video framed without faces
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
> **Scope:** frontend-design-system
|
|
4
4
|
> **Layer:** 3
|
|
5
|
-
> **Keywords:** video generation, seedance, img2vid, endImage, directional footage, loop video, E005 moderation, cost ladder
|
|
5
|
+
> **Keywords:** video generation, seedance, morph-assets, openrouter, img2vid, endImage, directional footage, loop video, E005 moderation, cost ladder, promoteFrom
|
|
6
6
|
> **Load When:** producing footage for a scroll-driven page or animating a client render
|
|
7
7
|
|
|
8
|
-
**Verified against:**
|
|
8
|
+
**Verified against:** morph-assets MCP (generate_video, generate_loop_video, get_video, library, media_tool) over OpenRouter; models `bytedance/seedance-1-5-pro` and `bytedance/seedance-2.0`. Last-verified: 2026-09-27.
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -38,6 +38,13 @@ Give a start image and an end image, and let the model interpolate a camera trav
|
|
|
38
38
|
1. Pick or generate the **start frame**: the wide shot, the resting state.
|
|
39
39
|
2. Pick or generate the **end frame** (`endImage`): the destination of the movement (a detail in close-up, a revealed facade).
|
|
40
40
|
3. Call `generate_video { prompt, image: start, endImage: end }`. The prompt describes the camera movement between the two.
|
|
41
|
+
4. Check the **last-frame QA** in the response (SSIM of the real last frame against `endImage`): ✅ respected, ⚠️ does not match, ≈ inconclusive. A ⚠️ means the model ignored `endImage` and the take will not scrub as designed.
|
|
42
|
+
|
|
43
|
+
**Models that respect `endImage` (verified 2026-09-27):** Seedance 1.5 Pro and Seedance 2.0. Use them for scrub.
|
|
44
|
+
|
|
45
|
+
- `seedance-2.0-mini` does **not** respect `endImage` (it overshoots the end frame). Do not use it for scrub.
|
|
46
|
+
- Veo 3.1 Lite/Fast came out **inconclusive** on the last-frame check. Do not rely on them for scrub until re-verified.
|
|
47
|
+
- `aspectRatio: "adaptive"` does not exist on OpenRouter: omit `aspectRatio` to inherit the ratio of the start frame.
|
|
41
48
|
|
|
42
49
|
**Movements that scrub well:**
|
|
43
50
|
|
|
@@ -52,11 +59,12 @@ Give a start image and an end image, and let the model interpolate a camera trav
|
|
|
52
59
|
|
|
53
60
|
## Section 3: Faces Trip Moderation
|
|
54
61
|
|
|
55
|
-
Img2vid with a **human face** repeatedly fails moderation (error E005). The workaround that works is to **crop the face out of frame** before generating.
|
|
62
|
+
Img2vid with a **human face** repeatedly fails moderation (it surfaced as error E005 on Replicate and still trips moderation through OpenRouter). The workaround that works is to **crop the face out of frame** before generating.
|
|
56
63
|
|
|
57
64
|
- Frame from the neck down (garment, hands, fabric) rather than the whole person. For tailoring, craft and manufacturing this usually improves the shot, because the product becomes the subject.
|
|
58
65
|
- Hands working (at a cuff, a button, the material) pass reliably and carry the craft story.
|
|
59
66
|
- If the client insists on a face, generate the **static image** with it (image generation is more permissive) and reserve video for the face-free shots.
|
|
67
|
+
- **Video has no ZDR (zero data retention) on OpenRouter:** the provider may retain the frames. Nothing sensitive in them (documents, screens with client data, identifiable people).
|
|
60
68
|
|
|
61
69
|
---
|
|
62
70
|
|
|
@@ -64,8 +72,10 @@ Img2vid with a **human face** repeatedly fails moderation (error E005). The work
|
|
|
64
72
|
|
|
65
73
|
Every generation costs money and you rarely nail the movement on the first attempt.
|
|
66
74
|
|
|
67
|
-
1. **Draft:**
|
|
68
|
-
2. **Final:** only after the draft is approved,
|
|
75
|
+
1. **Draft:** the default model (`bytedance/seedance-1-5-pro`), 480p (the default), 4-5s. About US$0.05 per take; enough to judge framing and movement. Ambient loops can stay on this model.
|
|
76
|
+
2. **Final:** only after the draft is approved, promote it: `generate_video { promoteFrom: "<draft id>", model: "seedance", resolution: "1080p" }` renders `bytedance/seedance-2.0` at 1080p with the **same prompt, frames and seed** (about US$1.50 for 4s). Then re-extract frames and swap them into the build **with a cache-bust** (`?v=N`), or the browser serves the old ones. See `frontend/scroll-driven/frame-scrub.md`.
|
|
77
|
+
|
|
78
|
+
To choose between models before drafting, `models: ["a", "b"]` runs the same request on 2-3 models with the cost of each. Any generation above US$2, over the project budget or with an unknown price returns `CONFIRMATION_REQUIRED` + `confirmToken`: get human approval, then repeat the same call with the token. `dryRun: true` only estimates.
|
|
69
79
|
|
|
70
80
|
Never open at 1080p.
|
|
71
81
|
|
|
@@ -95,7 +105,11 @@ TECHNICAL: img2vid with image (start) + endImage (end). No audio. No watermark.
|
|
|
95
105
|
|
|
96
106
|
## Section 6: Async Contract
|
|
97
107
|
|
|
98
|
-
`generate_video` and `generate_loop_video`
|
|
108
|
+
`generate_video` and `generate_loop_video` wait up to `waitSeconds` (default 60) and, if the job is still running, return "⏳ generating" with the job id rather than a file. Poll with `get_video { ids? }` (several ids at once; no ids = every pending job), which downloads the `.mp4` once the job completes. **Never resend `generate_video` while a job is running**: an identical request within 15 minutes returns the same job, but a different one is billed again.
|
|
109
|
+
|
|
110
|
+
`library { op: "pending" | "list" | "info" }` reads local history and spends no credits (it replaces the old `list_videos`). `usage { op: "balance" }` checks the OpenRouter key without spending credits, which makes it the safe health check for the MCP.
|
|
111
|
+
|
|
112
|
+
Post-production runs locally with no cost through `media_tool` (ffmpeg): `scrub_frames` (frames + `frames.json`, each re-extraction creates a new `v{N}` folder, which doubles as the cache-bust), `poster` (static image for mobile), `flip` (hflip when the subject collides with the copy), `concat`, `compress` (web background) and `picker` (the side-by-side approval page for the gate).
|
|
99
113
|
|
|
100
114
|
---
|
|
101
115
|
|
|
@@ -104,15 +118,16 @@ TECHNICAL: img2vid with image (start) + endImage (end). No audio. No watermark.
|
|
|
104
118
|
- [ ] Directional footage (start differs from end) for anything scroll-driven
|
|
105
119
|
- [ ] Seamless loop used only for a self-playing ambient plane
|
|
106
120
|
- [ ] Client renders animated, never AI-recreated
|
|
107
|
-
- [ ] Faces cropped out of frame before img2vid
|
|
121
|
+
- [ ] Faces cropped out of frame before img2vid; nothing sensitive in the frames (no ZDR)
|
|
122
|
+
- [ ] Scrub footage generated on a model that respects `endImage` (Seedance 1.5 Pro / 2.0, never `seedance-2.0-mini`) and the last-frame QA came back ✅
|
|
108
123
|
- [ ] Movement chosen from the scrub-friendly list, no orbits or returns
|
|
109
124
|
- [ ] Prompt reserves a calm region for copy, subject opposite the text
|
|
110
|
-
- [ ] Draft at 480p
|
|
125
|
+
- [ ] Draft at 480p approved before any 1080p render, and the final made with `promoteFrom`
|
|
111
126
|
- [ ] Frames re-extracted with a bumped cache-bust after the final render
|
|
112
127
|
|
|
113
128
|
## Boundary: Runtime Generation Is Another Standard
|
|
114
129
|
|
|
115
|
-
This standard is **authoring time**: a human driving the
|
|
130
|
+
This standard is **authoring time**: a human driving the morph-assets MCP in a session, footage that ends
|
|
116
131
|
up committed as a page asset. When the **product itself** generates video at runtime — a .NET worker
|
|
117
132
|
creating a paid task, polling an operation, downloading bytes into its own blob store — the standard
|
|
118
133
|
is `ai-agents/media-video.md` (Veo via `Google.GenAI`, the HTTP-only providers, cost per second, the
|
|
@@ -116,8 +116,10 @@ happy path + one error path per component above is that budget applied to a comp
|
|
|
116
116
|
- **Shared helpers by default.** Reuse the existing wrappers (`test/helpers.tsx`), MSW handlers and
|
|
117
117
|
factories. Before writing a new one, look for it (`morph-spec graph explain <Name>`; fallback
|
|
118
118
|
without a graph: `grep -rn`).
|
|
119
|
-
- **
|
|
120
|
-
reducers, schema)
|
|
119
|
+
- **Order is free, proof is not the implementer's:** a bug gets a regression test, and pure logic
|
|
120
|
+
(formatters, reducers, schema) gets its tests, in whatever order the code comes out. The
|
|
121
|
+
implementer does not run red→green cycles nor mutate production code to watch a test fail; it
|
|
122
|
+
runs its own filter once before the report, and the Gate 3 mutation proof shows the test bites.
|
|
121
123
|
|
|
122
124
|
## Common Mistakes
|
|
123
125
|
|