Skip to content

Add developer toolbar and AI generation tools - #9

Merged
vercel[bot] merged 3 commits into
devfrom
v0/toolbar-palette-ui
Oct 8, 2026
Merged

vercel[bot] merged 3 commits into
devfrom
v0/toolbar-palette-ui

Conversation

@vercel

@vercel vercel Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor
  • Added a new developer toolbar and integrated AI generation tools directly into the extension.
  • Updated the extension toolbar color palette to align with the Vercel design system.
  • Implemented a dedicated color palette for the website DevTools toolbar.

v0 Session

Summary by Sourcery

Integrate a Vercel-inspired developer toolbar and AI generation suite into the extension and web application.

New Features:

  • Add a Vercel-styled developer toolbar with searchable tool navigation, workspace context, shortcuts, and keyboard controls.
  • Add AI generation tools for text, structured objects, images, speech, and video/storyboards.
  • Support optional Vercel AI Gateway integration with browser-stored or deployment-provided API keys and local demo fallbacks.

Enhancements:

  • Refresh the extension DevTools toolbar layout and color palette to match the Vercel-inspired dark design.

Build:

  • Allow all hosts for the Vite development server.

Tests:

  • Add unit and end-to-end coverage for AI Gateway key management and the new generation tools.

v0 added 3 commits October 8, 2026 09:58
Register five new tools in the AI category, each following the existing
tool layout with a service, unit tests and an e2e spec.

Services call the Vercel AI Gateway when a key is available and otherwise
fall back to a local deterministic generator, so every tool stays usable
without any credentials. The key can be entered per browser and takes
precedence over the optional VITE_AI_GATEWAY_API_KEY build-time variable,
which keeps secrets out of the public bundle.
@vercel
vercel Bot merged commit 727b1e0 into dev Oct 8, 2026
1 of 4 checks passed
@vercel
vercel Bot deleted the v0/toolbar-palette-ui branch October 8, 2026 10:12
@vercel

vercel Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor Author

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
enclosed Error Error Oct 8, 2026 10:12am UTC

@sourcery-ai

sourcery-ai Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

The PR adds a globally mounted Vercel-inspired developer toolbar and extension redesign, exposes searchable shortcuts for newly registered AI tools, and implements configurable AI Gateway integration with local deterministic or browser-based fallbacks plus unit and end-to-end coverage.

Sequence diagram for AI generation with gateway fallback

sequenceDiagram
  actor User
  participant Toolbar as VercelToolbar
  participant Tool as GenerationTool
  participant Service as GenerationService
  participant Gateway as AI_Gateway
  participant Fallback as Local_or_browser_fallback

  User->>Toolbar: Search or select AI tool
  Toolbar->>Tool: router.push(path)
  User->>Tool: Submit prompt
  Tool->>Service: generateText/generateObject/generateImage/generateSpeech/generateVideo
  Service->>Gateway: callAiGateway(request)
  alt gateway succeeds
    Gateway-->>Service: Generated result
  else key missing or request fails
    Service->>Fallback: Build demo result or browser preview
    Fallback-->>Service: Local result
  end
  Service-->>Tool: Result and source
  Tool-->>User: Display generated output
Loading

File-Level Changes

Change Details Files
Adds a Vercel-styled in-app developer toolbar for discovering and launching tools.
  • Mounts the toolbar globally in the application shell.
  • Provides searchable tool navigation, keyboard shortcuts, AI-tool shortcuts, workspace metadata, and responsive styling.
  • Registers click handling for new extension toolbar shortcuts and updates the extension palette/layout.
src/App.vue
src/components/VercelToolbar.vue
extension/toolbar.html
extension/toolbar.js
Introduces configurable AI Gateway access with secure local key management and deployment-level configuration.
  • Adds an optional build-time gateway key and browser-local user key with user override and clear controls.
  • Centralizes authenticated modality requests against the AI Gateway endpoint.
  • Adds unit coverage for key storage, trimming, precedence-related state, and clearing.
env.d.ts
src/components/AiGatewaySettings.vue
src/composable/aiGatewayKey.ts
src/composable/aiGatewayKey.test.ts
src/utils/ai-gateway.ts
vite.config.ts
Adds five AI generation tools with gateway-backed generation and deterministic offline fallbacks.
  • Registers text, object, image, speech, and video tools in the AI category.
  • Implements modality-specific prompts, model/options, result rendering, downloads or copy actions, and gateway/demo source messaging.
  • Provides local deterministic text, JSON, SVG image, browser speech, and video storyboard fallbacks when gateway generation is unavailable.
src/tools/index.ts
src/tools/generate-text/index.ts
src/tools/generate-text/generate-text.service.ts
src/tools/generate-text/generate-text.vue
src/tools/generate-text/generate-text.e2e.spec.ts
src/tools/generate-text/generate-text.service.test.ts
src/tools/generate-object/index.ts
src/tools/generate-object/generate-object.service.ts
src/tools/generate-object/generate-object.vue
src/tools/generate-object/generate-object.e2e.spec.ts
src/tools/generate-object/generate-object.service.test.ts
src/tools/generate-image/index.ts
src/tools/generate-image/generate-image.service.ts
src/tools/generate-image/generate-image.vue
src/tools/generate-image/generate-image.e2e.spec.ts
src/tools/generate-image/generate-image.service.test.ts
src/tools/generate-speech/index.ts
src/tools/generate-speech/generate-speech.service.ts
src/tools/generate-speech/generate-speech.vue
src/tools/generate-speech/generate-speech.e2e.spec.ts
src/tools/generate-speech/generate-speech.service.test.ts
src/tools/generate-video/index.ts
src/tools/generate-video/generate-video.service.ts
src/tools/generate-video/generate-video.vue
src/tools/generate-video/generate-video.e2e.spec.ts
src/tools/generate-video/generate-video.service.test.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai 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.

Hey - I've found 11 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src/composable/aiGatewayKey.ts" line_range="9" />
<code_context>
+ * Key baked at build time, for self-hosted deployments where the site operator owns the key.
+ * It is public by nature, so a key entered by the user always takes precedence over it.
+ */
+const envApiKey = (import.meta.env.VITE_AI_GATEWAY_API_KEY as string | undefined) ?? '';
+
+/** Key entered by the user in the interface, kept in local storage and never shipped in the bundle */
</code_context>
<issue_to_address>
**Deployment key exposed**

When the deployment sets `VITE_AI_GATEWAY_API_KEY` to a credential intended to remain secret, vite embeds the key in the client bundle, and `callAiGateway` sends it as a Bearer token, so any site visitor can extract and reuse it to consume the deployment’s AI Gateway quota.

Keep deployment credentials server-side and route Gateway requests through a server endpoint instead of exposing the key in the client bundle.
</issue_to_address>

### Comment 2
<location path="vite.config.ts" line_range="24" />
<code_context>
 // https://vitejs.dev/config/
 export default defineConfig({
+  server: {
+    allowedHosts: true,
+  },
   plugins: [
</code_context>
<issue_to_address>
**Development server exposed**

When a developer runs the Vite server on a reachable interface while an attacker can use DNS rebinding, `allowedHosts: true` accepts arbitrary Host headers, allowing DNS rebinding to reach the development server and access served source or other dev-server resources.

Replace `allowedHosts: true` with an explicit allowlist of trusted hosts.
</issue_to_address>

### Comment 3
<location path="src/utils/ai-gateway.ts" line_range="33" />
<code_context>
+    throw new Error('No AI Gateway API key configured');
+  }
+
+  const response = await fetch(`${aiGatewayBaseUrl}/${request.modality}`, {
+    method: 'POST',
+    headers: {
</code_context>
<issue_to_address>
**Gateway generation fails**

When a user configures a key and tries to generate content, `callAiGateway` sends modality-specific `/v1/{modality}` requests with a custom `{ modality, model, prompt }` body instead of the Gateway’s supported inference endpoint and request format. The requests fail, and the generation services return demo output instead of generated content.

Use the Gateway’s supported inference endpoint and request format for the generation requests.
</issue_to_address>

### Comment 4
<location path="src/tools/generate-speech/generate-speech.vue" line_range="52" />
<code_context>
+    isGenerating.value = false;
+  }
+
+  await play();
+}
+
</code_context>
<issue_to_address>
**Generated audio is ignored**

When the Gateway returns generated audio, `onGenerate` calls `play()`, which uses browser speech synthesis on the text instead of playing the generated `audio`. Users hear browser-generated speech rather than the selected model’s output.

Play the generated audio when it is available, and use browser speech synthesis only when no generated audio is available.

Also at `src/tools/generate-speech/generate-speech.vue:58`.
</issue_to_address>

### Comment 5
<location path="src/tools/generate-image/generate-image.service.ts" line_range="127" />
<code_context>
+      size: options.size,
+    });
+
+    const image = String(data.image ?? data.url ?? '');
+
+    if (image === '') {
</code_context>
<issue_to_address>
**Remote media is unusable**

When the Gateway returns a remote media URL instead of inline base64 data, the image and video download composables treat remote URLs as base64, producing invalid downloads, while the speech service also treats URLs as base64, so its audio cannot play.

Handle remote media URLs as URLs instead of passing them to base64-only download and playback paths.

Also at `src/tools/generate-speech/generate-speech.service.ts:103`, `src/tools/generate-video/generate-video.service.ts:100`.
</issue_to_address>

### Comment 6
<location path="src/tools/generate-object/generate-object.service.ts" line_range="131" />
<code_context>
   if (item.dataset.path !== '') openTool(item.dataset.path);
-  else openTool('');
-});
+}
+
+toolSections.addEventListener('click', handleToolClick);
</code_context>
<issue_to_address>
**Structured results are discarded**

When the Gateway returns `object` as an object or `scenes` as an array, the object and video services convert structured values to strings before parsing them, so parsing throws and the catch blocks replace valid Gateway results with demo output; the video fallback also discards the generated video.

Parse native structured values directly, and only parse JSON strings when the Gateway returns text.

Also at `src/tools/generate-video/generate-video.service.ts:106`.
</issue_to_address>

### Comment 7
<location path="src/tools/generate-text/generate-text.service.ts" line_range="80" />
<code_context>
+      prompt: buildTextPrompt(options),
+    });
+
+    return { source: 'gateway', text: String(data.text ?? '') };
+  }
+  catch {
</code_context>
<issue_to_address>
**Text result is empty**

When the Gateway responds successfully without non-empty text, `generateText` returns an empty string as a successful Gateway result, so the UI hides the result card and shows no explanation.

Treat missing or empty `data.text` as an unusable response and return an appropriate failure or fallback result.
</issue_to_address>

### Comment 8
<location path="src/tools/generate-speech/generate-speech.service.ts" line_range="56" />
<code_context>
+    return Promise.resolve(false);
+  }
+
+  synthesis.cancel();
+
+  return new Promise((resolve) => {
</code_context>
<issue_to_address>
**Stop control disables early**

When the user starts another playback before the previous utterance finishes, a canceled utterance can finish after a newer one starts, and its `play()` completion sets `isSpeaking` to false while the newer utterance is still speaking. The Stop button then becomes disabled.

Ensure only the current playback can update `isSpeaking` when its promise completes.

Also at `src/tools/generate-speech/generate-speech.vue:61`.
</issue_to_address>

### Comment 9
<location path="src/tools/generate-text/generate-text.service.ts" line_range="82" />
<code_context>
+
+    return { source: 'gateway', image };
+  }
+  catch {
+    return { source: 'demo', image: buildDemoImage(options) };
+  }
</code_context>
<issue_to_address>
**Gateway failures lose diagnostic details**

When a Gateway request fails or returns an unusable response, each service catches Gateway, parsing, and response errors without logging or reporting them, then returns a demo result. The UI only identifies the result as locally generated, leaving operators without the cause or any signal that real generation is failing.

Report the failure through observable client-side telemetry or an error state while retaining the demo fallback.

Also at `src/tools/generate-object/generate-object.service.ts:132`, `src/tools/generate-image/generate-image.service.ts:135`, `src/tools/generate-speech/generate-speech.service.ts:111`, `src/tools/generate-video/generate-video.service.ts:108`.
</issue_to_address>

### Comment 10
<location path="src/tools/generate-video/generate-video.service.ts" line_range="60" />
<code_context>
+  const subject = keywords[0] ?? 'the subject';
+  const detail = keywords[1] ?? keywords[0] ?? 'the setting';
+  const sceneCount = countScenes(duration);
+  const scenesDuration = Math.round((duration / sceneCount) * 10) / 10;
+
+  return Array.from({ length: sceneCount }, (_, index): VideoScene => {
</code_context>
<issue_to_address>
**Storyboard duration is wrong**

When the local storyboard is generated for an 8- or 10-second duration, `buildDemoStoryboard` rounds each scene duration to a tenth and uses that value for every scene, so the displayed timeline totals 8.1 or 9.9 seconds instead of the selected duration.

Distribute scene durations so their total matches the requested duration.
</issue_to_address>

### Comment 11
<location path="src/tools/generate-object/generate-object.e2e.spec.ts" line_range="15" />
<code_context>
+  test('Generates a JSON object', async ({ page }) => {
+    await page.getByRole('button', { name: 'Generate object' }).click();
+
+    await expect(page.getByText('Generated object')).toBeVisible();
+  });
+
</code_context>
<issue_to_address>
**Empty object passes generation test**

When `generateObject` returns an empty object, the “Generates a JSON object” test only checks that the result card title is visible; `onGenerate` serializes `{}` to the truthy string `'{}'`, which renders that card, so the test passes even though no requested fields were generated.

Assert that the rendered JSON contains at least the requested `title` field and a value.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 11 findings to address first, and the change sends bearer API keys from the browser to a new external AI Gateway and stores user-entered keys in localStorage; a deployment key is also compiled into the client bundle. If that handling is wrong, keys may already be exposed or misused before a revert, so reverting does not undo the resulting credential exposure.

Blocking findings: src/composable/aiGatewayKey.ts:9, vite.config.ts:24, src/utils/ai-gateway.ts:33, src/tools/generate-speech/generate-speech.vue:52, src/tools/generate-image/generate-image.service.ts:127, and 6 more


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

* Key baked at build time, for self-hosted deployments where the site operator owns the key.
* It is public by nature, so a key entered by the user always takes precedence over it.
*/
const envApiKey = (import.meta.env.VITE_AI_GATEWAY_API_KEY as string | undefined) ?? '';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Critical · Deployment key exposed

When the deployment sets VITE_AI_GATEWAY_API_KEY to a credential intended to remain secret, vite embeds the key in the client bundle, and callAiGateway sends it as a Bearer token, so any site visitor can extract and reuse it to consume the deployment’s AI Gateway quota.

Keep deployment credentials server-side and route Gateway requests through a server endpoint instead of exposing the key in the client bundle.

Prompt for AI agents
In `src/composable/aiGatewayKey.ts` at line 9:

**Deployment key exposed**

When the deployment sets `VITE_AI_GATEWAY_API_KEY` to a credential intended to remain secret, vite embeds the key in the client bundle, and `callAiGateway` sends it as a Bearer token, so any site visitor can extract and reuse it to consume the deployment’s AI Gateway quota.

Keep deployment credentials server-side and route Gateway requests through a server endpoint instead of exposing the key in the client bundle.

Comment thread vite.config.ts
// https://vitejs.dev/config/
export default defineConfig({
server: {
allowedHosts: true,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 High · Development server exposed

When a developer runs the Vite server on a reachable interface while an attacker can use DNS rebinding, allowedHosts: true accepts arbitrary Host headers, allowing DNS rebinding to reach the development server and access served source or other dev-server resources.

Replace allowedHosts: true with an explicit allowlist of trusted hosts.

Prompt for AI agents
In `vite.config.ts` at line 24:

**Development server exposed**

When a developer runs the Vite server on a reachable interface while an attacker can use DNS rebinding, `allowedHosts: true` accepts arbitrary Host headers, allowing DNS rebinding to reach the development server and access served source or other dev-server resources.

Replace `allowedHosts: true` with an explicit allowlist of trusted hosts.

Comment thread src/utils/ai-gateway.ts
throw new Error('No AI Gateway API key configured');
}

const response = await fetch(`${aiGatewayBaseUrl}/${request.modality}`, {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 High · Gateway generation fails

When a user configures a key and tries to generate content, callAiGateway sends modality-specific /v1/{modality} requests with a custom { modality, model, prompt } body instead of the Gateway’s supported inference endpoint and request format. The requests fail, and the generation services return demo output instead of generated content.

Use the Gateway’s supported inference endpoint and request format for the generation requests.

Prompt for AI agents
In `src/utils/ai-gateway.ts` at line 33:

**Gateway generation fails**

When a user configures a key and tries to generate content, `callAiGateway` sends modality-specific `/v1/{modality}` requests with a custom `{ modality, model, prompt }` body instead of the Gateway’s supported inference endpoint and request format. The requests fail, and the generation services return demo output instead of generated content.

Use the Gateway’s supported inference endpoint and request format for the generation requests.

isGenerating.value = false;
}

await play();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 High · Generated audio is ignored

When the Gateway returns generated audio, onGenerate calls play(), which uses browser speech synthesis on the text instead of playing the generated audio. Users hear browser-generated speech rather than the selected model’s output.

Play the generated audio when it is available, and use browser speech synthesis only when no generated audio is available.

Also at src/tools/generate-speech/generate-speech.vue:58.

Prompt for AI agents
In `src/tools/generate-speech/generate-speech.vue` at line 52:

**Generated audio is ignored**

When the Gateway returns generated audio, `onGenerate` calls `play()`, which uses browser speech synthesis on the text instead of playing the generated `audio`. Users hear browser-generated speech rather than the selected model’s output.

Play the generated audio when it is available, and use browser speech synthesis only when no generated audio is available.

Also at `src/tools/generate-speech/generate-speech.vue:58`.

size: options.size,
});

const image = String(data.image ?? data.url ?? '');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 High · Remote media is unusable

When the Gateway returns a remote media URL instead of inline base64 data, the image and video download composables treat remote URLs as base64, producing invalid downloads, while the speech service also treats URLs as base64, so its audio cannot play.

Handle remote media URLs as URLs instead of passing them to base64-only download and playback paths.

Also at src/tools/generate-speech/generate-speech.service.ts:103, src/tools/generate-video/generate-video.service.ts:100.

Prompt for AI agents
In `src/tools/generate-image/generate-image.service.ts` at line 127:

**Remote media is unusable**

When the Gateway returns a remote media URL instead of inline base64 data, the image and video download composables treat remote URLs as base64, producing invalid downloads, while the speech service also treats URLs as base64, so its audio cannot play.

Handle remote media URLs as URLs instead of passing them to base64-only download and playback paths.

Also at `src/tools/generate-speech/generate-speech.service.ts:103`, `src/tools/generate-video/generate-video.service.ts:100`.

prompt: buildTextPrompt(options),
});

return { source: 'gateway', text: String(data.text ?? '') };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · Text result is empty

When the Gateway responds successfully without non-empty text, generateText returns an empty string as a successful Gateway result, so the UI hides the result card and shows no explanation.

Treat missing or empty data.text as an unusable response and return an appropriate failure or fallback result.

Prompt for AI agents
In `src/tools/generate-text/generate-text.service.ts` at line 80:

**Text result is empty**

When the Gateway responds successfully without non-empty text, `generateText` returns an empty string as a successful Gateway result, so the UI hides the result card and shows no explanation.

Treat missing or empty `data.text` as an unusable response and return an appropriate failure or fallback result.

return Promise.resolve(false);
}

synthesis.cancel();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · Stop control disables early

When the user starts another playback before the previous utterance finishes, a canceled utterance can finish after a newer one starts, and its play() completion sets isSpeaking to false while the newer utterance is still speaking. The Stop button then becomes disabled.

Ensure only the current playback can update isSpeaking when its promise completes.

Also at src/tools/generate-speech/generate-speech.vue:61.

Prompt for AI agents
In `src/tools/generate-speech/generate-speech.service.ts` at line 56:

**Stop control disables early**

When the user starts another playback before the previous utterance finishes, a canceled utterance can finish after a newer one starts, and its `play()` completion sets `isSpeaking` to false while the newer utterance is still speaking. The Stop button then becomes disabled.

Ensure only the current playback can update `isSpeaking` when its promise completes.

Also at `src/tools/generate-speech/generate-speech.vue:61`.


return { source: 'gateway', text: String(data.text ?? '') };
}
catch {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · Gateway failures lose diagnostic details

When a Gateway request fails or returns an unusable response, each service catches Gateway, parsing, and response errors without logging or reporting them, then returns a demo result. The UI only identifies the result as locally generated, leaving operators without the cause or any signal that real generation is failing.

Report the failure through observable client-side telemetry or an error state while retaining the demo fallback.

Also at src/tools/generate-object/generate-object.service.ts:132, src/tools/generate-image/generate-image.service.ts:135, src/tools/generate-speech/generate-speech.service.ts:111, src/tools/generate-video/generate-video.service.ts:108.

Prompt for AI agents
In `src/tools/generate-text/generate-text.service.ts` at line 82:

**Gateway failures lose diagnostic details**

When a Gateway request fails or returns an unusable response, each service catches Gateway, parsing, and response errors without logging or reporting them, then returns a demo result. The UI only identifies the result as locally generated, leaving operators without the cause or any signal that real generation is failing.

Report the failure through observable client-side telemetry or an error state while retaining the demo fallback.

Also at `src/tools/generate-object/generate-object.service.ts:132`, `src/tools/generate-image/generate-image.service.ts:135`, `src/tools/generate-speech/generate-speech.service.ts:111`, `src/tools/generate-video/generate-video.service.ts:108`.

const subject = keywords[0] ?? 'the subject';
const detail = keywords[1] ?? keywords[0] ?? 'the setting';
const sceneCount = countScenes(duration);
const scenesDuration = Math.round((duration / sceneCount) * 10) / 10;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · Storyboard duration is wrong

When the local storyboard is generated for an 8- or 10-second duration, buildDemoStoryboard rounds each scene duration to a tenth and uses that value for every scene, so the displayed timeline totals 8.1 or 9.9 seconds instead of the selected duration.

Distribute scene durations so their total matches the requested duration.

Prompt for AI agents
In `src/tools/generate-video/generate-video.service.ts` at line 60:

**Storyboard duration is wrong**

When the local storyboard is generated for an 8- or 10-second duration, `buildDemoStoryboard` rounds each scene duration to a tenth and uses that value for every scene, so the displayed timeline totals 8.1 or 9.9 seconds instead of the selected duration.

Distribute scene durations so their total matches the requested duration.

test('Generates a JSON object', async ({ page }) => {
await page.getByRole('button', { name: 'Generate object' }).click();

await expect(page.getByText('Generated object')).toBeVisible();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · Empty object passes generation test

When generateObject returns an empty object, the “Generates a JSON object” test only checks that the result card title is visible; onGenerate serializes {} to the truthy string '{}', which renders that card, so the test passes even though no requested fields were generated.

Assert that the rendered JSON contains at least the requested title field and a value.

Prompt for AI agents
In `src/tools/generate-object/generate-object.e2e.spec.ts` at line 15:

**Empty object passes generation test**

When `generateObject` returns an empty object, the “Generates a JSON object” test only checks that the result card title is visible; `onGenerate` serializes `{}` to the truthy string `'{}'`, which renders that card, so the test passes even though no requested fields were generated.

Assert that the rendered JSON contains at least the requested `title` field and a value.

This branch had an error being deployed

1 failed and 2 active deployments
Preview – v0-project-review-tools — 1ad5f235 Deployed Oct 8, 2026 by vercel[bot]
Preview – devtools — 1ad5f235 Deployed Oct 8, 2026 by vercel[bot]
Preview – enclosed — 1ad5f235 Deployed Oct 8, 2026 by vercel[bot]
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