Restore app shell and add ltx-2 support#50
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Reviewer's GuideRestores the full app shell and landing page experience, introduces first-class support for the LTX-2 video model across configuration, pricing, schemas, SEO, and tests, and hardens the Pollinations OAuth callback to depend on Convex auth readiness while cleaning up temporary maintenance-mode plumbing and adding Convex dev helpers. Sequence diagram for Pollinations OAuth callback with Convex auth gatingsequenceDiagram
actor User
participant Browser
participant PollinationsCallbackPage
participant ConvexAuth
participant ConvexMutation
participant ConvexBackend
User->>Browser: Navigate to /auth/pollinations/callback
Browser->>PollinationsCallbackPage: Render React component
PollinationsCallbackPage->>ConvexAuth: useConvexAuth()
ConvexAuth-->>PollinationsCallbackPage: isAuthLoading, isAuthenticated
PollinationsCallbackPage->>PollinationsCallbackPage: useEffect watches isAuthLoading, isAuthenticated, processCallback
PollinationsCallbackPage->>PollinationsCallbackPage: if isAuthLoading is true, return early
PollinationsCallbackPage->>PollinationsCallbackPage: setTimeout 100ms
PollinationsCallbackPage->>PollinationsCallbackPage: after 100ms, check isAuthenticated
alt User not authenticated
PollinationsCallbackPage->>PollinationsCallbackPage: setState error_save_failed
else User authenticated
PollinationsCallbackPage->>ConvexMutation: setPollinationsApiKey(extractedKey)
ConvexMutation->>ConvexBackend: save Pollinations API key
ConvexBackend-->>ConvexMutation: success
ConvexMutation-->>PollinationsCallbackPage: success
PollinationsCallbackPage->>PollinationsCallbackPage: setState success
end
PollinationsCallbackPage->>Browser: Render success or error UI
Browser-->>User: Show result and redirect countdown
Updated class diagram for LTX-2 model configuration and pricingclassDiagram
class ModelDefinition {
+string id
+string displayName
+string type
+string icon
+string description
+Constraints constraints
+VideoDurationConstraints durationConstraints
+string[] aspectRatios
+boolean supportsNegativePrompt
+boolean supportsReferenceImage
+ModelPricingDefinition modelPricing
}
class Constraints {
+number maxPixels
+number minPixels
+number minDimension
+number maxDimension
+number step
+Dimensions defaultDimensions
+boolean dimensionsEnabled
+boolean supportsSeed
+string[] supportedTiers
+string outputCertainty
+string dimensionWarning
}
class Dimensions {
+number width
+number height
}
class VideoDurationConstraints {
+number min
+number max
+number defaultDuration
}
class ModelPricingDefinition {
+string modelId
+string type
+number approximatePerPollen
+boolean supportsReferenceImage
+boolean isAlpha
+VideoPricing videoPricing
}
class VideoPricing {
+number perSecond
}
class MODEL_REGISTRY {
+Record~string, ModelDefinition~ entries
}
class VIDEO_MODEL_PRICING {
+Record~string, ModelPricingDefinition~ entries
}
class VideoModelSchema {
+veo
+seedance
+seedance_pro
+wan
+ltx_2
+grok_video
}
class VideoGenerationParamsSchema {
+string prompt
+string model
+number duration
+string aspectRatio
+boolean audio
+string lastFrameImage
}
class PollinationsUrlParams {
+string prompt
+string model
+string image
+string lastFrameImage
+number duration
+string aspectRatio
+boolean audio
}
class MODEL_SEO_SLUGS {
+ModelSlugEntry[] entries
}
class ModelSlugEntry {
+string modelId
+string slug
+string displayName
+string type
+string[] categories
}
MODEL_REGISTRY --> ModelDefinition : contains
ModelDefinition --> Constraints
Constraints --> Dimensions
ModelDefinition --> VideoDurationConstraints
ModelDefinition --> ModelPricingDefinition
VIDEO_MODEL_PRICING --> ModelPricingDefinition : contains
ModelPricingDefinition --> VideoPricing
VideoGenerationParamsSchema --> VideoModelSchema : uses
PollinationsUrlParams --> VideoModelSchema : uses
MODEL_SEO_SLUGS --> ModelSlugEntry : contains
class LTX2ModelDefinition {
+id = ltx-2
+displayName = LTX-2
+type = video
+icon = video
+description
+constraints
+durationConstraints
+aspectRatios
+supportsNegativePrompt = false
+supportsReferenceImage = false
+modelPricing
}
class LTX2Constraints {
+maxPixels = 1048576
+minPixels = 65536
+minDimension = 256
+maxDimension = 1024
+step = 32
+defaultDimensions = 1024x576
+dimensionsEnabled = false
+supportsSeed = false
+supportedTiers = sd
+outputCertainty = exact
+dimensionWarning
}
class LTX2DurationConstraints {
+min = 1
+max = 10
+defaultDuration = 5
}
class LTX2PricingDefinition {
+modelId = ltx-2
+type = video
+approximatePerPollen = 40
+supportsReferenceImage = false
+isAlpha = true
+videoPricing
}
class LTX2VideoPricing {
+perSecond = 0.005
}
MODEL_REGISTRY --> LTX2ModelDefinition : entry ltx-2
LTX2ModelDefinition --> LTX2Constraints
LTX2ModelDefinition --> LTX2DurationConstraints
LTX2ModelDefinition --> LTX2PricingDefinition
LTX2PricingDefinition --> LTX2VideoPricing
VIDEO_MODEL_PRICING --> LTX2PricingDefinition : entry ltx-2
class LTX2SeoEntry {
+modelId = ltx-2
+slug = ltx-2
+displayName = LTX-2
+type = video
+categories = create,features
}
MODEL_SEO_SLUGS --> LTX2SeoEntry : includes ltx-2
Flow diagram for Pollinations URL building with LTX-2 and video modelsflowchart TD
A[Start buildPollinationsUrl] --> B[Receive PollinationsUrlParams]
B --> C{Has model and model is in VIDEO_MODELS}
C -->|No| Z[Build image URL params]
C -->|Yes| D[Video model branch]
D --> E{Model is veo and has image and lastFrameImage}
E -->|Yes| F[Append image param with image and lastFrameImage joined by pipe]
E -->|No| G{Has image}
G -->|Yes| H[Append image param with image only]
G -->|No| I[Skip image param]
F --> J
H --> J
I --> J
J{Has duration and duration > 0} -->|Yes| K[Append duration param]
J -->|No| L[Skip duration param]
K --> M
L --> M
M{Has aspectRatio} -->|Yes| N[Append aspect_ratio param]
M -->|No| O[Skip aspect_ratio]
N --> P[Handle other common params]
O --> P
P --> Q[Construct final URL]
Z --> Q
Q --> R[Return URL string]
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 3 minutes and 17 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughCallback handling now separates client-side hash parsing/clearing from a user-confirmed server action that persists the Pollinations API key; layout and middleware conditional rendering removed; landing page refactored into composed sections; a new ltx-2 video model is added across registry, pricing, schemas, and URL logic; assorted tests and Convex dev scripts added. Changes
Sequence Diagram(s)sequenceDiagram
participant Browser as Browser (client)
participant UI as Callback UI
participant Action as Server Action (savePollinationsApiKey)
participant Auth as Clerk/Convex Auth
participant Convex as Convex Mutation
Browser->>UI: mount with URL hash (`#api_key`=...)
UI->>UI: parse hash, validate format
UI->>Browser: clear URL hash (replaceState)
UI-->>Browser: show "Finish Connection" (ready)
Browser->>UI: user clicks "Finish Connection"
UI->>Action: call savePollinationsApiKey(apiKey)
Action->>Auth: getConvexClerkToken()
Auth-->>Action: token (or null)
alt token available
Action->>Convex: fetchMutation(api.users.setPollinationsApiKey, {apiKey}, {token})
Convex-->>Action: mutation result
Action-->>UI: { status: "success" } or error
else no token / error
Action-->>UI: { status: "error_save_failed" } or validation error
end
UI->>Browser: render success or error UI
Estimated code review effort🎯 4 (Complex) | ⏱️ ~40 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Review Summary by QodoRestore app shell and add LTX-2 video model support
WalkthroughsDescription• Add LTX-2 video model support with full metadata and constraints • Restore full app shell and landing page, removing maintenance mode • Update Pollinations callback to wait for Convex auth readiness • Refine video model reference image handling for interpolation-only models Diagramflowchart LR
A["LTX-2 Model Definition"] -->|"metadata, constraints, pricing"| B["Model Registry"]
B -->|"per-second pricing"| C["Video Pricing Schema"]
B -->|"1MP, 1-10s duration"| D["API Constraints"]
E["Pollinations URL Builder"] -->|"reference image logic"| F["Video Model Handling"]
F -->|"interpolation only for Veo"| G["URL Parameters"]
H["App Layout"] -->|"remove maintenance mode"| I["Full App Shell"]
I -->|"restore providers"| J["Landing Page + Studio"]
K["Pollinations Callback"] -->|"wait for auth"| L["Convex Auth Ready"]
L -->|"then save API key"| M["User Profile"]
File Changes1. lib/config/models.ts
|
Code Review by Qodo
|
| /** Video model IDs - these are the only models that accept video-specific query params */ | ||
| const VIDEO_MODELS = ["veo", "seedance", "seedance-pro", "wan", "ltx-2", "grok-video"] as const |
There was a problem hiding this comment.
2. video_models uses as const 📘 Rule violation ⚙ Maintainability
The updated VIDEO_MODELS definition uses a TypeScript as const assertion on a changed line, which is disallowed by the TypeScript assertion compliance rule. This reduces type-safety guarantees by allowing assertion-based typing rather than narrowing/validation.
Agent Prompt
## Issue description
A changed line uses `as const` (`VIDEO_MODELS = [...] as const`), which violates the rule to avoid `as` type assertions.
## Issue Context
This constant can be typed without assertions (e.g., as a `readonly string[]`) since it is only used for membership checks.
## Fix Focus Areas
- convex/lib/pollinations.ts[17-18]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| constraints: { | ||
| maxPixels: 1_048_576, | ||
| minPixels: 65_536, | ||
| minDimension: 256, | ||
| maxDimension: 1024, | ||
| step: 32, | ||
| defaultDimensions: { width: 1024, height: 576 }, | ||
| dimensionsEnabled: false, | ||
| supportsSeed: false, | ||
| supportedTiers: ["sd"], | ||
| outputCertainty: "exact", | ||
| dimensionWarning: "Uses fixed 16:9 or 9:16 frames rounded to multiples of 32", | ||
| }, | ||
| aspectRatios: VIDEO_ASPECT_RATIOS, | ||
| supportsNegativePrompt: false, | ||
| supportsReferenceImage: false, |
There was a problem hiding this comment.
4. Ltx-2 ratio/constraint mismatch 🐞 Bug ≡ Correctness
The ltx-2 model sets maxDimension=1024/maxPixels=1,048,576 but uses VIDEO_ASPECT_RATIOS (HD 1920×1080 / 1080×1920). Because the settings hook treats single-tier fixed-size models as exact, ltx-2 will be initialized with out-of-constraint dimensions that users cannot adjust (dimensionsEnabled=false), likely breaking generation or forcing upstream clamping.
Agent Prompt
## Issue description
The new `ltx-2` model uses `VIDEO_ASPECT_RATIOS` (HD dimensions), but its constraints cap dimensions to 1024 and pixels to 1MP. The generation settings hook will pick the first ratio's width/height for single-tier fixed models, resulting in invalid/unadjustable dimensions.
## Issue Context
`dimensionsEnabled: false` prevents the user from correcting dimensions. This can lead to provider errors or silent clamping and incorrect UX.
## Fix Focus Areas
- lib/config/models.ts[867-905]
- lib/config/models.ts[266-272]
- hooks/use-generation-settings.ts[328-337]
## Expected fix
- Define a dedicated `LTX2_ASPECT_RATIOS` with exact sizes that satisfy constraints (e.g., 1024×576 and 576×1024, step 32) and use it for `ltx-2`.
- Alternatively/additionally, change the fixed-size-model branch in `use-generation-settings` to prefer `newConstraints.defaultDimensions` (or clamp ratio dims to constraints) instead of blindly using `ratios[0].width/height`.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ebd56c6cfe
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (!isAuthenticated) { | ||
| setState("error_save_failed"); | ||
| return; |
There was a problem hiding this comment.
Clear OAuth hash before unauthenticated early return
When isAuthenticated is false, this branch sets error_save_failed and returns before processCallback() runs, but processCallback() is the only place that removes #api_key=... from the URL (app/auth/pollinations/callback/page.tsx:151-159). If a user’s Clerk/Convex auth is not ready after redirect (or their session expired), the API key remains in the address bar and can be leaked via copy/paste, browser history, or screenshots; the hash should be cleared even on this error path.
Useful? React with 👍 / 👎.
| }, | ||
| aspectRatios: VIDEO_ASPECT_RATIOS, | ||
| supportsNegativePrompt: false, | ||
| supportsReferenceImage: false, |
There was a problem hiding this comment.
Gate ltx-2 from reference-frame request fields
This commit introduces ltx-2 as supportsReferenceImage: false, but the generation flow still treats all video models as frame-capable: the frames UI is shown for any isVideoModel (components/studio/features/generation/controls-view.tsx:359-363) and request assembly always forwards videoReferenceImages[0]/[1] for video models (components/studio/layout/studio-shell.tsx:342-349 and 374-395). As a result, users can send stale or newly selected frame images with ltx-2, which contradicts its text-only contract and can cause failed generations.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (1)
lib/models/model-seo-slugs.test.ts (1)
27-30: Consider deriving expected video count instead of hardcoding2.This test will churn every time active video models change; deriving expected count from the active registry/filter would make it more resilient.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/models/model-seo-slugs.test.ts` around lines 27 - 30, Replace the hardcoded expected value (2) with a derived count from the authoritative active-model source: compute expectedVideoCount by querying the active registry/collection (e.g., MODEL_REGISTRY.getActiveModels() or the source list used to build MODEL_SEO_SLUGS) and filter for type === "video", then assert expect(videoEntries).toHaveLength(expectedVideoCount); reference MODEL_SEO_SLUGS and the registry method/collection you use to derive the expected count.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/auth/pollinations/callback/page.test.tsx`:
- Around line 39-42: The current test hardcodes useConvexAuth to {
isAuthenticated: true, isLoading: false } so it never exercises the new
loading/unauthenticated gate; change the mock exported from
vi.mock("convex/react") to allow configuring the return value (e.g., expose a
configurable get/set or a mock implementation that reads a variable) instead of
always returning the same object, and update tests that use mockSetApiKey to add
two new cases: one where useConvexAuth returns { isLoading: true } to assert the
component remains in processing and does not call useMutation/mockSetApiKey, and
one where it returns { isAuthenticated: false, isLoading: false } to assert the
failure UI is shown and the callback-hash stripping still occurs.
In `@app/auth/pollinations/callback/page.tsx`:
- Around line 174-192: The effect that waits on isAuthLoading may leave the
Pollinations API key in window.location.hash during auth startup or when
unauthenticated; before returning or bailing out in the useEffect (the callback
that references isAuthLoading, isAuthenticated, and processCallback), clear the
hash so the secret isn't leaked. Add a line at the top of that useEffect to
remove the fragment (e.g., history.replaceState(null, '',
window.location.pathname + window.location.search) or window.location.hash = '')
and also clear it right before the early return when !isAuthenticated, ensuring
processCallback (which already strips the hash) still functions as expected.
In `@app/layout.tsx`:
- Around line 152-158: The root layout currently always renders Header, causing
duplicate navbars because app/page.tsx mounts LandingHeader; update the layout
to avoid rendering Header for landing routes by either moving Header out of
app/layout.tsx into a route-specific layout (e.g., an "app" sublayout) or add a
route check that suppresses Header for the landing path. Locate the Header usage
in app/layout.tsx and either relocate it to the appropriate sublayout (so
LandingHeader remains in app/page.tsx) or wrap the Header render in a
pathname/route conditional that excludes the root (“/”) and any marketing
routes; ensure LandingHeader (referenced in app/page.tsx) remains unaffected.
In `@convex/lib/pollinations.test.ts`:
- Around line 38-51: The test for buildPollinationsUrl currently only asserts
the primary "image" param; add a negative assertion that the
last-frame/interpolation query params are not present so the test truly verifies
"does not encode last frame image" behavior—after creating parsed (new URL(url))
add assertions like expect(parsed.searchParams.get("lastFrameImage")).toBeNull()
and/or expect(parsed.searchParams.has("last_frame")).toBe(false) (and similarly
for any interpolation-related keys your buildPollinationsUrl might emit) to
ensure no last-frame or interpolation params are encoded.
---
Nitpick comments:
In `@lib/models/model-seo-slugs.test.ts`:
- Around line 27-30: Replace the hardcoded expected value (2) with a derived
count from the authoritative active-model source: compute expectedVideoCount by
querying the active registry/collection (e.g., MODEL_REGISTRY.getActiveModels()
or the source list used to build MODEL_SEO_SLUGS) and filter for type ===
"video", then assert expect(videoEntries).toHaveLength(expectedVideoCount);
reference MODEL_SEO_SLUGS and the registry method/collection you use to derive
the expected count.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: d13df503-d0bc-429f-89d8-b45f4eca4718
📒 Files selected for processing (19)
app/auth/pollinations/callback/page.test.tsxapp/auth/pollinations/callback/page.tsxapp/layout.tsxapp/page.tsxcomponents/studio/upgrade-modal.test.tsxconvex/lib/pollinations.test.tsconvex/lib/pollinations.tshooks/queries/use-image-models.test.tslib/config/api.config.tslib/config/models.test.tslib/config/models.tslib/models/model-seo-slugs.test.tslib/models/model-seo-slugs.tslib/schemas/pollinations-pricing.schema.test.tslib/schemas/pollinations-pricing.schema.tslib/schemas/pollinations.schema.test.tslib/schemas/pollinations.schema.tspackage.jsonproxy.ts
👮 Files not reviewed due to content moderation or server errors (5)
- lib/schemas/pollinations.schema.ts
- lib/schemas/pollinations-pricing.schema.ts
- lib/config/models.ts
- convex/lib/pollinations.ts
- lib/config/models.test.ts
| vi.mock("convex/react", () => ({ | ||
| useMutation: () => mockSetApiKey, | ||
| useConvexAuth: () => ({ isAuthenticated: true, isLoading: false }), | ||
| })); |
There was a problem hiding this comment.
Make the Convex auth mock configurable and cover the new gate.
Hardcoding useConvexAuth() to { isAuthenticated: true, isLoading: false } means this suite never exercises the newly added isAuthLoading and unauthenticated branches. That leaves the security-sensitive callback gating untested.
💡 Small test setup tweak
const mockSetApiKey = vi.fn().mockResolvedValue({ success: true });
+const mockUseConvexAuth = vi.fn(() => ({
+ isAuthenticated: true,
+ isLoading: false,
+}));
vi.mock("convex/react", () => ({
useMutation: () => mockSetApiKey,
- useConvexAuth: () => ({ isAuthenticated: true, isLoading: false }),
+ useConvexAuth: () => mockUseConvexAuth(),
}));Then add explicit cases for:
isLoading: true→ stays in processing and does not call the mutation yet.isAuthenticated: false→ shows the failure path and still strips the callback hash.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| vi.mock("convex/react", () => ({ | |
| useMutation: () => mockSetApiKey, | |
| useConvexAuth: () => ({ isAuthenticated: true, isLoading: false }), | |
| })); | |
| const mockSetApiKey = vi.fn().mockResolvedValue({ success: true }); | |
| const mockUseConvexAuth = vi.fn(() => ({ | |
| isAuthenticated: true, | |
| isLoading: false, | |
| })); | |
| vi.mock("convex/react", () => ({ | |
| useMutation: () => mockSetApiKey, | |
| useConvexAuth: () => mockUseConvexAuth(), | |
| })); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/auth/pollinations/callback/page.test.tsx` around lines 39 - 42, The
current test hardcodes useConvexAuth to { isAuthenticated: true, isLoading:
false } so it never exercises the new loading/unauthenticated gate; change the
mock exported from vi.mock("convex/react") to allow configuring the return value
(e.g., expose a configurable get/set or a mock implementation that reads a
variable) instead of always returning the same object, and update tests that use
mockSetApiKey to add two new cases: one where useConvexAuth returns { isLoading:
true } to assert the component remains in processing and does not call
useMutation/mockSetApiKey, and one where it returns { isAuthenticated: false,
isLoading: false } to assert the failure UI is shown and the callback-hash
stripping still occurs.
| <ClerkThemeProvider> | ||
| <ConvexClientProvider> | ||
| <QueryProvider> | ||
| <PollenAuthProvider> | ||
| <Header /> | ||
| {children} | ||
| <Toaster position="bottom-right" richColors closeButton /> |
There was a problem hiding this comment.
Avoid rendering both app and landing headers on /.
app/page.tsx Line 55 already mounts LandingHeader, so the unconditional Header here gives the home page two top nav bars / auth entry points. Please move the app header into a route-specific layout (for example, app vs. marketing groups) or otherwise suppress it for landing routes.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/layout.tsx` around lines 152 - 158, The root layout currently always
renders Header, causing duplicate navbars because app/page.tsx mounts
LandingHeader; update the layout to avoid rendering Header for landing routes by
either moving Header out of app/layout.tsx into a route-specific layout (e.g.,
an "app" sublayout) or add a route check that suppresses Header for the landing
path. Locate the Header usage in app/layout.tsx and either relocate it to the
appropriate sublayout (so LandingHeader remains in app/page.tsx) or wrap the
Header render in a pathname/route conditional that excludes the root (“/”) and
any marketing routes; ensure LandingHeader (referenced in app/page.tsx) remains
unaffected.
| it("does not encode last frame image for non-interpolation video models like ltx-2", () => { | ||
| const url = buildPollinationsUrl({ | ||
| prompt: "test prompt", | ||
| model: "ltx-2", | ||
| image: "https://example.com/first.jpg", | ||
| lastFrameImage: "https://example.com/second.jpg", | ||
| duration: 5, | ||
| aspectRatio: "16:9", | ||
| }) | ||
|
|
||
| const parsed = new URL(url) | ||
|
|
||
| expect(parsed.searchParams.get("image")).toBe("https://example.com/first.jpg") | ||
| }) |
There was a problem hiding this comment.
Test misses a negative assertion for last-frame encoding.
The test title says last-frame data is not encoded, but it only checks image. Add an explicit check that interpolation params are absent (Line 50 onward), otherwise this can pass on a partial regression.
✅ Tighten the test
const parsed = new URL(url)
expect(parsed.searchParams.get("image")).toBe("https://example.com/first.jpg")
+ expect(parsed.searchParams.get("image_urls")).toBeNull()📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| it("does not encode last frame image for non-interpolation video models like ltx-2", () => { | |
| const url = buildPollinationsUrl({ | |
| prompt: "test prompt", | |
| model: "ltx-2", | |
| image: "https://example.com/first.jpg", | |
| lastFrameImage: "https://example.com/second.jpg", | |
| duration: 5, | |
| aspectRatio: "16:9", | |
| }) | |
| const parsed = new URL(url) | |
| expect(parsed.searchParams.get("image")).toBe("https://example.com/first.jpg") | |
| }) | |
| it("does not encode last frame image for non-interpolation video models like ltx-2", () => { | |
| const url = buildPollinationsUrl({ | |
| prompt: "test prompt", | |
| model: "ltx-2", | |
| image: "https://example.com/first.jpg", | |
| lastFrameImage: "https://example.com/second.jpg", | |
| duration: 5, | |
| aspectRatio: "16:9", | |
| }) | |
| const parsed = new URL(url) | |
| expect(parsed.searchParams.get("image")).toBe("https://example.com/first.jpg") | |
| expect(parsed.searchParams.get("image_urls")).toBeNull() | |
| }) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@convex/lib/pollinations.test.ts` around lines 38 - 51, The test for
buildPollinationsUrl currently only asserts the primary "image" param; add a
negative assertion that the last-frame/interpolation query params are not
present so the test truly verifies "does not encode last frame image"
behavior—after creating parsed (new URL(url)) add assertions like
expect(parsed.searchParams.get("lastFrameImage")).toBeNull() and/or
expect(parsed.searchParams.has("last_frame")).toBe(false) (and similarly for any
interpolation-related keys your buildPollinationsUrl might emit) to ensure no
last-frame or interpolation params are encoded.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
components/studio/features/generation/controls-feature.tsx (1)
86-89: Centralize the reference-frame limit calculation.
useGenerationSettings()now computes the same limit, and the two versions already differ for non-video / unknown models (undefinedthere vs0here). Pull this into a shared helper or expose the computed max from the hook so the UI gate, local-storage clamp, and request payload stay in sync.Also applies to: 183-183
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/studio/features/generation/controls-feature.tsx` around lines 86 - 89, The UI computes maxReferenceFrames locally (using currentModelDef.supportsReferenceImage, supportsInterpolation, and referenceFrameCount) while useGenerationSettings() already computes this value differently; centralize the logic by either moving the calculation into a shared helper (e.g., getMaxReferenceFrames(modelDef)) or expose the computed max from useGenerationSettings() so the UI can consume it directly; update references to maxReferenceFrames in controls-feature.tsx (and the other occurrence) to use the shared helper or hook-provided value to ensure the UI gate, local-storage clamp, and request payload all use the same source of truth.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/auth/pollinations/callback/actions.ts`:
- Around line 47-49: The catch block in
app/auth/pollinations/callback/actions.ts currently logs the raw thrown object
(console.error("[PollinationsCallback] Error saving API key:", error)), which
may expose secrets; change this to log a sanitized identifier instead—replace
the raw error log with a minimal safe message and non-sensitive metadata (e.g.,
console.error("[PollinationsCallback] Error saving API key:", { errorName:
error?.name ?? "UnknownError" }) or log a redacted message like "{ message:
'save failed', errorName: error?.name }"), and ensure the thrown error object or
full stack is not included; keep the return { status: "error_save_failed" }
behavior intact.
In `@app/auth/pollinations/callback/page.tsx`:
- Around line 170-199: The effect currently advances to "ready" purely on a
valid apiKey; change it to gate the "ready" state on the authenticated
user/session state: still call clearUrlHash() early as shown, but inside the
timeout (the block using callbackHash, extractKeyFromHash, isValidApiKeyFormat,
setApiKey, setCallbackState) validate the app's auth indicator (e.g.,
currentUser, isAuthenticated, or session object) before calling
setCallbackState("ready") — if auth is unknown wait or set an auth-specific
error state (e.g., "error_not_authenticated") and do not call setApiKey or
"ready"; ensure any auth check is synchronous or handled via the same
timeout/async flow so the URL-hash clearing behavior is unchanged.
---
Nitpick comments:
In `@components/studio/features/generation/controls-feature.tsx`:
- Around line 86-89: The UI computes maxReferenceFrames locally (using
currentModelDef.supportsReferenceImage, supportsInterpolation, and
referenceFrameCount) while useGenerationSettings() already computes this value
differently; centralize the logic by either moving the calculation into a shared
helper (e.g., getMaxReferenceFrames(modelDef)) or expose the computed max from
useGenerationSettings() so the UI can consume it directly; update references to
maxReferenceFrames in controls-feature.tsx (and the other occurrence) to use the
shared helper or hook-provided value to ensure the UI gate, local-storage clamp,
and request payload all use the same source of truth.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 3d1c3449-9ebf-4091-9359-95cbf8b397a5
📒 Files selected for processing (13)
app/auth/pollinations/callback/actions.test.tsapp/auth/pollinations/callback/actions.tsapp/auth/pollinations/callback/page.test.tsxapp/auth/pollinations/callback/page.tsxcomponents/studio/features/generation/controls-feature.tsxcomponents/studio/features/generation/controls-view.test.tsxcomponents/studio/features/generation/controls-view.tsxcomponents/studio/layout/studio-shell.test.tsxcomponents/studio/layout/studio-shell.tsxconvex/lib/pollinations.test.tsconvex/lib/pollinations.tshooks/use-generation-settings.test.tshooks/use-generation-settings.ts
✅ Files skipped from review due to trivial changes (1)
- hooks/use-generation-settings.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- convex/lib/pollinations.test.ts
- app/auth/pollinations/callback/page.test.tsx
- convex/lib/pollinations.ts
| } catch (error) { | ||
| console.error("[PollinationsCallback] Error saving API key:", error); | ||
| return { status: "error_save_failed" }; |
There was a problem hiding this comment.
Avoid logging the raw exception in the API-key save path.
This handler processes a secret, and dumping the full thrown object to server logs can expose the key or request metadata from downstream failures. Log a sanitized identifier instead.
Suggested fix
} catch (error) {
- console.error("[PollinationsCallback] Error saving API key:", error);
+ console.error("[PollinationsCallback] Error saving API key", {
+ errorName: error instanceof Error ? error.name : "unknown",
+ });
return { status: "error_save_failed" };
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| } catch (error) { | |
| console.error("[PollinationsCallback] Error saving API key:", error); | |
| return { status: "error_save_failed" }; | |
| } catch (error) { | |
| console.error("[PollinationsCallback] Error saving API key", { | |
| errorName: error instanceof Error ? error.name : "unknown", | |
| }); | |
| return { status: "error_save_failed" }; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/auth/pollinations/callback/actions.ts` around lines 47 - 49, The catch
block in app/auth/pollinations/callback/actions.ts currently logs the raw thrown
object (console.error("[PollinationsCallback] Error saving API key:", error)),
which may expose secrets; change this to log a sanitized identifier
instead—replace the raw error log with a minimal safe message and non-sensitive
metadata (e.g., console.error("[PollinationsCallback] Error saving API key:", {
errorName: error?.name ?? "UnknownError" }) or log a redacted message like "{
message: 'save failed', errorName: error?.name }"), and ensure the thrown error
object or full stack is not included; keep the return { status:
"error_save_failed" } behavior intact.
| useEffect(() => { | ||
| let callbackHash = window.location.hash; | ||
|
|
||
| if (callbackHash) { | ||
| // Clear the hash from the URL for security (prevent accidental sharing) | ||
| // before waiting for browser redirect timing quirks to settle. | ||
| clearUrlHash(); | ||
| } | ||
|
|
||
| const timer = window.setTimeout(() => { | ||
| if (!callbackHash && window.location.hash) { | ||
| callbackHash = window.location.hash; | ||
| clearUrlHash(); | ||
| } | ||
|
|
||
| const apiKey = extractKeyFromHash(callbackHash); | ||
|
|
||
| if (!apiKey) { | ||
| setState("error_missing_key"); | ||
| setCallbackState("error_missing_key"); | ||
| return; | ||
| } | ||
|
|
||
| // Validate key format | ||
| if (!isValidApiKeyFormat(apiKey)) { | ||
| setState("error_invalid_key"); | ||
| setCallbackState("error_invalid_key"); | ||
| return; | ||
| } | ||
|
|
||
| // Clear the hash from the URL for security (prevent accidental sharing) | ||
| // Do this before the async call to minimize exposure time | ||
| if (typeof window !== "undefined") { | ||
| window.history.replaceState( | ||
| null, | ||
| "", | ||
| window.location.pathname + window.location.search, | ||
| ); | ||
| } | ||
| setApiKey(apiKey); | ||
| setCallbackState("ready"); | ||
| }, PROCESSING_DELAY_MS); |
There was a problem hiding this comment.
Gate the "ready" state on authenticated user state.
A syntactically valid hash always advances to "ready" here, even if the user's session expired during the OAuth round-trip. In that case the hash is already stripped, the user clicks “Finish Connection”, and only then learns the save can never succeed. Keep clearing the hash early, but don't offer the confirm step until auth is known/valid.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/auth/pollinations/callback/page.tsx` around lines 170 - 199, The effect
currently advances to "ready" purely on a valid apiKey; change it to gate the
"ready" state on the authenticated user/session state: still call clearUrlHash()
early as shown, but inside the timeout (the block using callbackHash,
extractKeyFromHash, isValidApiKeyFormat, setApiKey, setCallbackState) validate
the app's auth indicator (e.g., currentUser, isAuthenticated, or session object)
before calling setCallbackState("ready") — if auth is unknown wait or set an
auth-specific error state (e.g., "error_not_authenticated") and do not call
setApiKey or "ready"; ensure any auth check is synchronous or handled via the
same timeout/async flow so the URL-hash clearing behavior is unchanged.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 92fa3cb109
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| outputCertainty: "exact", | ||
| dimensionWarning: "Uses fixed 16:9 or 9:16 frames rounded to multiples of 32", | ||
| }, | ||
| aspectRatios: VIDEO_ASPECT_RATIOS, |
There was a problem hiding this comment.
Define LTX-2 aspect-ratio presets within model limits
The ltx-2 model points to VIDEO_ASPECT_RATIOS, which uses HD presets (1920×1080 / 1080×1920) that exceed this model’s own maxDimension (1024) and 1MP budget declared in the same block. Because useGenerationSettings.handleModelChange() applies aspect-ratio preset dimensions for single-tier fixed-size models, selecting LTX-2 initializes oversized dimensions and StudioShell forwards them in generation requests. That makes this model prone to invalid requests (or provider-side clamping) instead of the intended 1024×576 / 576×1024 behavior.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0670b04591
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const [pendingDispatch, dispatched, processingDispatch] = await Promise.all([ | ||
| ctx.db | ||
| .query("pendingGenerations") | ||
| .withIndex("by_status", (q) => q.eq("status", "pending")) | ||
| .collect(), | ||
| .withIndex("by_dispatch_status", (q) => | ||
| q.eq("dispatchStatus", "pending").lt("updatedAt", cutoff) |
There was a problem hiding this comment.
Handle undefined dispatchStatus in stuck-generation cleanup
cleanupStuckGenerations now only queries by_dispatch_status for pending, dispatched, and processing, so pending/processing rows with dispatchStatus unset are never considered for timeout failure. That state is still explicitly supported elsewhere (claimGenerationForWorker accepts dispatchStatus === undefined), so legacy or partially migrated records can remain permanently active instead of being cleaned up. Add a fallback for undefined dispatch status (or a status-based fallback query) so old stuck jobs are still retired.
Useful? React with 👍 / 👎.
Summary
Verification
Note: build passes; Clerk emits its expected warning about a test publishable key being present in the production build environment.
Summary by Sourcery
Restore the full authenticated app shell and landing page while adding first-class support for the LTX-2 video model across config, pricing, schemas, SEO, and tests.
New Features:
Bug Fixes:
Enhancements:
Build:
Tests:
Summary by CodeRabbit
New Features
Improvements
Chores