Skip to content

Two-step thumbnail picker (choose text + frame, then render) + title-select fix#28

Merged
nmbrthirteen merged 4 commits into
mainfrom
feat/thumbnail-picker
Jun 19, 2026
Merged

Two-step thumbnail picker (choose text + frame, then render) + title-select fix#28
nmbrthirteen merged 4 commits into
mainfrom
feat/thumbnail-picker

Conversation

@nmbrthirteen

@nmbrthirteen nmbrthirteen commented Jun 19, 2026

Copy link
Copy Markdown
Owner

What

Reworks the clip detail Thumbnail section from a one-shot "generate 3 variations" into a pick-then-render flow, per request: first text choices, then frame choices, select both, generate — with re-iteration and a progress indicator.

Flow

  1. Get options → fetches headline text candidates (1 AI call) + face frame candidates (no AI) for the clip.
  2. Click a text option (fills Line 1/2) and a frame (or Upload frame). Hint by the button: "Leave Line 1 & 2 empty to auto-write the text."
  3. Generate → renders one final thumbnail from the chosen frame + text, bakes it into the clip. Shows a Rendering… indicator.
  4. Refresh options → re-rolls both lists to iterate.

Backend

  • New CLI commands: thumbnail-options (wraps generate_headline_variations + extract_candidate_frames) and thumbnail-render (wraps generate_thumbnail_with_template with line1/2 overrides + frame face-info).
  • New endpoints: GET /api/clips/:id/thumbnail/options, POST /api/clips/:id/thumbnail/render (route through the CLI, so they honor PODCLI_BACKEND and work in the bundled runtime).

Also: title-select fix

Clicking a generated title in Titles & description set the title silently (the field is in a section above, so it looked dead). Now it shows a selected state + a "Save to apply" hint.

Verification

  • thumbnail-render exercised end-to-end with the runtime Python/Node: renders the chosen text in the brand style (white headline + cyan highlight + frame + logo).
  • Backend function signatures verified against calls; cli.py compiles; root + client tsc clean; studio bundle builds.

Summary by CodeRabbit

  • New Features

    • Enhanced thumbnail generation workflow with selectable text and frame options before rendering.
    • Added thumbnail preview refresh and frame upload capabilities in clip editing.
  • UI/UX Improvements

    • Streamlined configuration page status display and token management.
    • Updated integration setup interface with improved path detection and Claude Desktop configuration guidance.
    • Improved clip detail editing interface with thumbnail options selection and frame preview display.

Replaces the one-shot "generate 3 variations" thumbnail UI with a
pick-then-render flow on the clip detail page:

- "Get options" fetches headline text candidates (one AI call) and face
  frame candidates (no AI) for the clip.
- Pick a text option (fills Line 1/2) and a frame, or upload your own
  frame; leaving the lines empty lets the AI write them.
- "Generate" renders one final thumbnail from the chosen frame + text and
  bakes it into the clip; a rendering indicator shows progress.
- "Refresh options" re-rolls both lists for iteration.

Backend: new `thumbnail-options` and `thumbnail-render` CLI commands wrap
generate_headline_variations / extract_candidate_frames /
generate_thumbnail_with_template; new
GET/POST /api/clips/:id/thumbnail/{options,render} endpoints.

Also fix title-option selection feedback: clicking a generated title now
shows a selected state and a "Save to apply" hint (it set the title
silently before, off-screen, so it looked broken).
@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@nmbrthirteen, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 41 minutes and 46 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0bb58b77-5995-47d6-bee6-bce410956188

📥 Commits

Reviewing files that changed from the base of the PR and between d5abd58 and a4906fd.

📒 Files selected for processing (2)
  • src/ui/public/css/styles.css
  • src/ui/web-server.ts
📝 Walkthrough

Walkthrough

Two new CLI subcommands (thumbnail-options, thumbnail-render) are added to backend/cli.py and wired through two new web server routes (GET /thumbnail/options, POST /thumbnail/render). ClipDetail is rebuilt around an options-then-render flow. Path resolution for backendDir and the MCP server path is improved to probe packaged runtime locations. McpSetupPage and ConfigPage UI copy and layout are updated accordingly.

Changes

Thumbnail options/render pipeline

Layer / File(s) Summary
Backend CLI thumbnail commands and dispatch
backend/cli.py
Adds cmd_thumbnail_options (emits candidate texts/frames as JSON) and cmd_thumbnail_render (renders final PNG, exits non-zero on failure), registers their argparse subcommands, and routes main() dispatch to both.
Web server thumbnail/options and thumbnail/render routes
src/ui/web-server.ts
Adds GET /api/clips/:id/thumbnail/options (invokes CLI, clamps counts, returns options JSON) and POST /api/clips/:id/thumbnail/render (invokes CLI, bakes result into clip, updates thumbnail_config, returns preview_path).
ClipDetail thumbnail options/render UI
src/ui/client/ClipDetail.tsx, src/ui/public/css/styles.css
Replaces thumbImage/thumbTimestamp state with textOpts/frameOpts/selFrame; adds loadOptions, uploadFrame, renderThumb helpers; rebuilds thumbnail section with options-fetch, generate, upload-frame controls, selectable text/frame pickers; adds .title-option.selected CSS rule.

Path resolution and MCP/Config UI updates

Layer / File(s) Summary
backendDir and MCP server path resolution
src/config/paths.ts, src/handlers/integrations.routes.ts
resolveBackendDir() probes cli.py under projectRoot/backend and projectRoot/runtime/backend; /api/integration-info computes mcp_path from PODCLI_STUDIO, packaged runtime/studio/mcp-server.mjs, or dist/index.js fallback, returning it as mcp_path/dist_path with updated server_ok.
McpSetupPage path and status updates
src/ui/client/McpSetupPage.tsx
Stores mcpPath from /integration-info; uses "Ready"/"Not built" status labels; injects mcpPath into Claude Desktop JSON args; changes Claude Code command to static podcli mcp install; removes inline instruction text.
ConfigPage status rows, Secrets, and Actions UI
src/ui/client/ConfigPage.tsx, src/ui/public/css/styles.css
Reduces status rows to "Cache"; updates Secrets token/placeholder text; replaces per-secret helper block with "Get token" anchor; reorders Actions buttons; changes checkbox label to "Use imported profile"; adds password input form-control and .set-link CSS.

Sequence Diagram(s)

sequenceDiagram
  participant Browser as ClipDetail (Browser)
  participant WebServer as web-server.ts
  participant CLI as backend/cli.py

  rect rgba(70, 130, 180, 0.5)
    note over Browser,CLI: Step 1 — Fetch options
    Browser->>WebServer: GET /api/clips/:id/thumbnail/options
    WebServer->>CLI: thumbnail-options --title ... --video ... --n-texts --n-frames
    CLI-->>WebServer: JSON {texts: [...], frames: [...]}
    WebServer-->>Browser: candidate texts + frame paths
  end

  rect rgba(100, 160, 100, 0.5)
    note over Browser,CLI: Step 2 — Render thumbnail
    Browser->>WebServer: POST /api/clips/:id/thumbnail/render (frame_path, line1, line2)
    WebServer->>CLI: thumbnail-render --frame ... --output ... --line1 --line2
    CLI-->>WebServer: JSON {output_path}
    WebServer->>CLI: bake-thumbnail + clips edit thumbnail_config
    WebServer-->>Browser: {ok: true, preview_path}
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • nmbrthirteen/podcli#5: Prior thumbnail tooling changes to backend/cli.py and the related UI/server code that this PR's thumbnail-options/thumbnail-render flow builds directly upon.
  • nmbrthirteen/podcli#27: Overlaps with this PR's src/config/paths.ts backendDir probing, integrations.routes.ts dynamic mcp_path, and McpSetupPage switch from distPath to mcpPath.

Poem

🐇 Hop, hop, I fetch the frames so bright,
Two steps to thumbnail, rendered right!
A face, a headline, chosen with care,
The mcp path resolved from thin air.
From runtime to dist, the bunny knows best —
podcli mcp install does the rest! 🎨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: a two-step thumbnail picker workflow (choose text + frame, then render) and a fix for the title selection feedback. It is concise, specific, and clearly reflects the primary objectives of the pull request.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/thumbnail-picker

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/ui/web-server.ts (1)

1487-1489: Guard clip.output_path before calling existsSync to avoid deprecation warnings.

fs.existsSync() does not throw when passed undefined in current Node.js versions (it returns false), but this behavior is deprecated and will likely throw in future major versions. While line 1487 works today, the same pattern appears unguarded at lines 1414 and 1435. Add a null-check guard to all three locations:

Suggested fix for line 1487
-  if (existsSync(clip.output_path)) {
+  if (clip.output_path && existsSync(clip.output_path)) {
     const bake = await bakeThumbnailCard(clip.output_path, outPath, tc.card_seconds || 0);

Also apply the same guard at lines 1414 and 1435 for consistency. Note: The String() wrapper is unnecessary since output_path is already typed as string (or defaults to empty string at initialization).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ui/web-server.ts` around lines 1487 - 1489, Add null-check guards before
calling existsSync with clip.output_path at all three locations where this
pattern appears in the code. Verify that clip.output_path is truthy before
passing it to existsSync to prevent deprecated behavior of passing undefined to
the function. Since output_path is already typed as string, avoid using String()
wrapper and simply check the value directly before the existsSync call.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/ui/client/McpSetupPage.tsx`:
- Around line 45-53: The desktopJson variable uses a template literal to
directly interpolate the serverPath into a JSON string, which causes invalid
JSON on Windows systems because backslashes in file paths are not escaped.
Instead of using a template literal, create a JavaScript object with the
appropriate structure (containing mcpServers with podcli configuration including
command "node" and args array with serverPath), then serialize it using
JSON.stringify() to automatically handle proper escaping of all special
characters including backslashes for cross-platform compatibility.

In `@src/ui/web-server.ts`:
- Around line 1491-1493: The runCli call for the clips edit command on line 1492
does not check whether the command succeeded or failed. If the metadata
persistence operation fails, the endpoint still returns ok: true, leaving the
clip state inconsistent. After the runCli call with the clips edit command,
check the result to verify success and only return the success response with ok:
true if the command executed successfully. If the command fails, throw an error
or return an appropriate error response instead of continuing to the success
response.
- Around line 1472-1477: The current validation of frame_path only checks
existence with existsSync, which does not prevent path traversal attacks.
Replace the existence check with a security validation that first resolves the
frame_path to its absolute form using the resolve function, then validates it is
within allowed directories (such as clip thumbnail or upload roots) before
allowing it to be used in the args array for the thumbnail-render command. This
ensures that crafted requests cannot point to arbitrary server paths.

---

Nitpick comments:
In `@src/ui/web-server.ts`:
- Around line 1487-1489: Add null-check guards before calling existsSync with
clip.output_path at all three locations where this pattern appears in the code.
Verify that clip.output_path is truthy before passing it to existsSync to
prevent deprecated behavior of passing undefined to the function. Since
output_path is already typed as string, avoid using String() wrapper and simply
check the value directly before the existsSync call.
🪄 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: 7cc12055-6145-47ad-9064-4ef02bb3a6d6

📥 Commits

Reviewing files that changed from the base of the PR and between 3ef4f4a and d5abd58.

📒 Files selected for processing (8)
  • backend/cli.py
  • src/config/paths.ts
  • src/handlers/integrations.routes.ts
  • src/ui/client/ClipDetail.tsx
  • src/ui/client/ConfigPage.tsx
  • src/ui/client/McpSetupPage.tsx
  • src/ui/public/css/styles.css
  • src/ui/web-server.ts

Comment thread src/ui/client/McpSetupPage.tsx Outdated
Comment thread src/ui/web-server.ts Outdated
Comment thread src/ui/web-server.ts
…ixes

- Resolve conflicts: take main's improved ConfigPage secret preview and
  McpSetupPage JSON.stringify config; keep main's resolveBackendDir().
- Apply the podcli landing 3D "surface" treatment to secondary buttons
  (.btn-ghost, .btn-danger) to match the primary 3D buttons.
- Remove a duplicate .copy-btn definition (kept the one with the copied state).
…, guard output_path

- thumbnail/render: resolve frame_path and require it under the clip's
  thumbnail dir or the upload dir before passing to the renderer.
- Fail the request if the clips-edit metadata write returns non-zero.
- Guard clip.output_path before existsSync.
(McpSetup Windows-path JSON was already resolved by taking JSON.stringify in the merge.)
@nmbrthirteen nmbrthirteen merged commit 5a924b3 into main Jun 19, 2026
5 checks passed
@nmbrthirteen nmbrthirteen deleted the feat/thumbnail-picker branch June 19, 2026 14:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant