Skip to content

Implemented ai sidebar feature end to end with spec generation - #11

Merged
Unmixeddiplomacy merged 5 commits into
mainfrom
dev
Jul 13, 2026
Merged

Unmixeddiplomacy merged 5 commits into
mainfrom
dev

Conversation

@Unmixeddiplomacy

@Unmixeddiplomacy Unmixeddiplomacy commented Jul 13, 2026 •

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features
    • Generate architecture designs from prompts with real-time collaborative canvas updates, shared run progress, and collaborator “thinking” indicators.
    • Added room-shared AI sidebar experiences (Architect design runs + Chat live prompt messages).
    • Generate, preview, list, and download project specifications as Markdown using secure, run-scoped access.
  • Bug Fixes
    • Hardened request validation/authorization with DB-backed rate limiting and deterministic, idempotent run IDs.
    • Added run-scoped public tokens with ownership checks; improved collaborative canvas syncing and cursor update staleness; safer canvas autosave timeouts.
  • Documentation
    • Expanded Trigger.dev guides for setup/configuration and orchestration patterns (streaming, tool calling, waitpoints, batch orchestration).

@coderabbitai

coderabbitai Bot commented Jul 13, 2026 •

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6638ab32-010e-407f-9187-e9e27a0a5a7e

📥 Commits

Reviewing files that changed from the base of the PR and between 0a4d92f and 93f47f1.

📒 Files selected for processing (2)
  • app/api/ai/design/route.ts
  • prisma/models/taskrun.prisma

📝 Walkthrough

Walkthrough

Adds Trigger.dev documentation and configuration, authenticated Gemini-backed AI design/spec generation, persistent project specifications, realtime AI sidebar features, and collaborative editor synchronization improvements.

Changes

Trigger.dev Documentation

Layer / File(s) Summary
Agent orchestration and AI references
.agents/skills/trigger-agents/SKILL.md, .agents/skills/trigger-agents/references/*
Documents agent orchestration, batching, streaming, waitpoints, and AI SDK tools.
Configuration and setup references
.agents/skills/trigger-config/*, .agents/skills/trigger-setup/*
Documents Trigger.dev configuration, build extensions, environment setup, task discovery, and project structure.

AI Generation and Persistence

Layer / File(s) Summary
Generation contracts and backend
liveblocks.config.ts, types/tasks.ts, prisma/models/*, lib/project-access.ts, app/api/ai/*, app/api/projects/..., src/trigger/*
Adds typed room events, validation, authorization, run/spec persistence, authenticated APIs, Gemini tasks, Liveblocks canvas updates, and private Blob-backed Markdown specs.
Feature specifications and tracking
context/feature-specs/*, context/progress-tracker.md
Records implementation requirements and completed work for AI generation, presence, chat, persistence, downloads, and UI integration.

Collaborative Editor and UI

Layer / File(s) Summary
Realtime AI sidebar
components/editor/ai-sidebar.tsx, components/editor/canvas/custom-cursor.tsx
Adds Architect, Chat, and Specs workflows with Liveblocks feeds, run tracking, progress states, previews, downloads, and thinking indicators.
Editor context and synchronization
components/editor/editor-shell.tsx, components/editor/collaborative-canvas.tsx, hooks/use-canvas-autosave.ts
Centralizes providers, guards saved-canvas loading, throttles cursor updates, and adds autosave timeout and payload-aware keepalive behavior.
Supporting API and presentation updates
app/api/projects/[projectId]/canvas/route.ts, components/editor/editor-navbar.tsx, components/editor/canvas/presence-avatars.tsx, app/globals.css, lib/prisma.ts, trigger.config.ts, package.json, tsconfig.json, .gitignore, skills-lock.json
Updates canvas validation and access handling, editor presentation, Prisma cleanup, Trigger configuration, dependencies, TypeScript scope, ignored files, and skill locking.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 51.72% 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 clearly matches the main change: an end-to-end AI sidebar workflow including spec generation.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev

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

@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: 19

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (16)
.agents/skills/trigger-agents/SKILL.md-12-23 (1)

12-23: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Specify a language for the fenced block.

Use ```text for the pattern-selection table to resolve the reported MD040 warning.

🤖 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 @.agents/skills/trigger-agents/SKILL.md around lines 12 - 23, Update the
fenced code block containing the pattern-selection table in the skill
documentation to declare the text language, using the repository’s expected
Markdown fence format so the MD040 warning is resolved.

Source: Linters/SAST tools

.agents/skills/trigger-agents/references/waitpoints.md-61-63 (1)

61-63: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Make timeout guidance consistent.

The complete Slack example throws on timeout, while the tips say to handle timeouts without throwing. Choose and document one policy.

Also applies to: 241-247

🤖 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 @.agents/skills/trigger-agents/references/waitpoints.md around lines 61 - 63,
Choose one timeout policy for the Slack waitpoint example and document it
consistently in both the complete example and the tips. Update the timeout
handling around the result.ok check so the implementation matches the documented
guidance, including the corresponding sections around the timeout tips.
.agents/skills/trigger-agents/references/waitpoints.md-40-40 (1)

40-40: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Describe maxDuration as compute time, not human-wait time.
maxDuration is active compute time; the waitpoint token timeout covers the human response window. Reword the 600 comment and the tips so they don’t imply human delay extends maxDuration.

🤖 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 @.agents/skills/trigger-agents/references/waitpoints.md at line 40, Update
the documentation around maxDuration in waitpoints.md to describe it strictly as
active compute time, including rewording the inline comment for 600 and related
tips. Clarify that the waitpoint token timeout covers the human response window,
without implying human delay extends maxDuration.

Source: MCP tools

.agents/skills/trigger-agents/references/streaming.md-123-144 (1)

123-144: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Update the useRealtimeStream example to the current signature. Pass progressStream as the first argument, read parts, and guard against an empty array before indexing; the current data/{ stream: "progress" } form is stale and can reach JSON.parse(undefined).

🤖 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 @.agents/skills/trigger-agents/references/streaming.md around lines 123 -
144, The Progress component’s useRealtimeStream example uses a stale API and can
parse an undefined value. Update useRealtimeStream to pass progressStream as its
first argument, consume the returned parts collection, and render the waiting
state when parts is empty before accessing its latest element.

Source: MCP tools

.agents/skills/trigger-agents/references/waitpoints.md-217-221 (1)

217-221: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Split the timeout branch from the approval branch. review.output?.feedback still reads output on the failed-result path; handle !review.ok first, then read review.output.feedback only after review.ok narrows the union.

🤖 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 @.agents/skills/trigger-agents/references/waitpoints.md around lines 217 -
221, Update the review result handling after wait.forToken in the approval flow:
handle !review.ok in a separate branch first, returning its failure feedback
without accessing output, then handle rejected approval using
review.output.feedback after review.ok narrows the union.
.agents/skills/trigger-setup/references/environment-setup.md-28-33 (1)

28-33: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Specify a language for the .gitignore fence.

Use gitignore for this fenced block so MD040 passes.

🤖 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 @.agents/skills/trigger-setup/references/environment-setup.md around lines 28
- 33, Specify the gitignore language on the fenced block in environment-setup.md
by changing its fence annotation to gitignore, while preserving the existing
.env entries.

Source: Linters/SAST tools

.agents/skills/trigger-setup/SKILL.md-90-98 (1)

90-98: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Specify a language for the project-tree fence.

Use text (or plaintext) for the fenced directory tree so MD040 passes.

🤖 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 @.agents/skills/trigger-setup/SKILL.md around lines 90 - 98, Update the
fenced project-tree example near the directory structure to specify the text
language, such as text or plaintext, immediately after the opening fence. Keep
the tree content unchanged.

Source: Linters/SAST tools

.agents/skills/trigger-setup/references/project-structure.md-79-93 (1)

79-93: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the task example copy-paste complete.

The snippet uses task() without importing it from @trigger.dev/sdk, so it does not compile as presented. Add the import before documenting it as a correct example.

🤖 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 @.agents/skills/trigger-setup/references/project-structure.md around lines 79
- 93, Add the missing import for task from `@trigger.dev/sdk` at the start of the
exported task example in the project structure documentation, so the snippet is
copy-paste complete while preserving the existing task examples.
.agents/skills/trigger-setup/references/project-structure.md-5-13 (1)

5-13: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Specify languages for all directory-tree fences.

Use text or plaintext for these three fenced trees to satisfy MD040.

Also applies to: 19-28, 51-61

🤖 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 @.agents/skills/trigger-setup/references/project-structure.md around lines 5
- 13, Add an explicit text or plaintext language identifier to each
directory-tree fenced code block in project-structure.md, including the trees
near the shown project layout and the additionally referenced sections, without
changing their contents.

Source: Linters/SAST tools

.agents/skills/trigger-config/references/config.md-338-346 (1)

338-346: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Qualify the deployment-only extension claim.

Some extensions also affect local development, so saying they only apply to deployment can mislead debugging expectations. additionalFiles and the Python development binary are examples.

🤖 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 @.agents/skills/trigger-config/references/config.md around lines 338 - 346,
Update the “Extensions only affect deployment” statement in the Best Practices
section to qualify that behavior: explain that extensions generally affect
deployment but some, including additionalFiles and the Python development
binary, also influence local development. Preserve the existing guidance about
using the external array.
.agents/skills/trigger-config/SKILL.md-231-240 (1)

231-240: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Qualify the extension scope claim. devPythonBinaryPath is a dev-mode setting, so “Extensions only affect deployment, not local development” is too broad. Make this extension-specific or narrow it to the extensions that are deployment-only.

🤖 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 @.agents/skills/trigger-config/SKILL.md around lines 231 - 240, Update the
“Extensions only affect deployment” statement in the Best Practices section to
qualify it as applying only to deployment-only extensions, preserving the
exception for dev-mode settings such as devPythonBinaryPath.

Source: MCP tools

.agents/skills/trigger-setup/SKILL.md-26-34 (1)

26-34: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use trigger.dev for the CLI examples. Replace npx trigger ... with npx trigger.dev@latest ... in this skill, including the init/dev/deploy commands.

🤖 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 @.agents/skills/trigger-setup/SKILL.md around lines 26 - 34, Update the CLI
examples in the trigger setup skill to invoke `trigger.dev@latest` instead of
`trigger`, including the init, dev, and deploy commands. Keep the existing
command arguments and surrounding setup instructions unchanged.

Source: MCP tools

components/editor/ai-sidebar.tsx-950-950 (1)

950-950: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Escape the literal quotes to satisfy react/no-unescaped-entities.

The unescaped " around Generate Spec is flagged by ESLint and can fail a strict lint/build gate.

🔧 Proposed fix
-                    Click "Generate Spec" to create a markdown technical spec from your canvas.
+                    Click "Generate Spec" to create a markdown technical spec from your canvas.
🤖 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 `@components/editor/ai-sidebar.tsx` at line 950, Update the instructional text
near the “Generate Spec” label in the AI sidebar to escape the literal quotation
marks in JSX, satisfying react/no-unescaped-entities while preserving the
displayed wording.

Source: Linters/SAST tools

src/trigger/generate-spec.ts-26-27 (1)

26-27: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Payload validation happens outside the try/catch, skipping failure metadata.

If generateSpecPayloadSchema.parse(payload) throws, metadata.set("status", "failed", ...) never runs, since the try block starts after this line. Any realtime consumer of run metadata (e.g. the AI sidebar) won't see a "failed" status for validation errors, only whatever the Trigger.dev run status reports separately.

🛡️ Proposed fix
-    // 1. Validate payload using Zod
-    const parsed = generateSpecPayloadSchema.parse(payload);
-    const { roomId, projectId, chatHistory, nodes, edges } = parsed;
-
     try {
+      // 1. Validate payload using Zod
+      const parsed = generateSpecPayloadSchema.parse(payload);
+      const { roomId, projectId, chatHistory, nodes, edges } = parsed;
+
       // 2. Set initial run metadata
🤖 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/trigger/generate-spec.ts` around lines 26 - 27, Move
generateSpecPayloadSchema.parse(payload) and the parsed-field destructuring into
the try block that handles generateSpec execution, ensuring validation failures
reach the existing failure handling and set metadata status to "failed".
Preserve the current parsed values and success path for valid payloads.
hooks/use-canvas-autosave.ts-151-158 (1)

151-158: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Measure keepalive size in bytes
finalStateStr.length counts UTF-16 code units, so non-ASCII canvas data can exceed the browser’s 64 KiB keepalive limit and make the final unmount save fail. Use TextEncoder().encode(finalStateStr).length for the gate.

🤖 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 `@hooks/use-canvas-autosave.ts` around lines 151 - 158, Update the keepalive
gate in the unmount autosave flow to measure the UTF-8 byte size of
finalStateStr using TextEncoder().encode(finalStateStr).length instead of the
JavaScript string length, while preserving the existing 64000-byte threshold and
fetch behavior.
components/editor/canvas/custom-cursor.tsx-41-49 (1)

41-49: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add a nameable role or hide the spinner. A bare span with aria-label isn’t reliably announced; use role="status"/role="img" if “AI thinking” should be exposed, or aria-hidden="true" if it’s decorative.

🤖 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 `@components/editor/canvas/custom-cursor.tsx` around lines 41 - 49, Update the
thinking spinner span in the custom cursor component to use an explicit
accessible role, such as role="status" for the “AI thinking” announcement, or
mark it aria-hidden="true" if the spinner is decorative; do not leave aria-label
on the bare span without a role.
🧹 Nitpick comments (15)
.agents/skills/trigger-agents/references/ai-tool.md (1)

7-44: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace the deprecated ai.tool examples throughout this doc. Use the AI SDK tool() helper with execute: ai.toolExecute(...), and import ai from @trigger.dev/sdk/ai. The tips section should also stop referring to ai.tool.

🤖 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 @.agents/skills/trigger-agents/references/ai-tool.md around lines 7 - 44,
Replace the deprecated ai.tool usage in the weatherTool example with the AI SDK
tool() helper and execute: ai.toolExecute(lookupWeather), importing tool from
the AI SDK and ai from `@trigger.dev/sdk/ai`. Update all remaining examples and
tips in this document to use the same pattern and remove references to ai.tool.

Source: MCP tools

.agents/skills/trigger-config/references/config.md (1)

125-135: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Avoid version: "latest" in reproducible-build guidance.

The document recommends pinning versions at Lines [340-344], but this example explicitly selects a floating version. Use a tested version or clearly label this as a non-reproducible development example.

🤖 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 @.agents/skills/trigger-config/references/config.md around lines 125 - 135,
Update the Lightpanda example using the lightpanda extension to remove the
floating version value: replace version "latest" with a tested pinned version,
or clearly label the snippet as a non-reproducible development example. Keep the
existing version and telemetry options otherwise unchanged.
app/api/ai/spec/route.ts (1)

18-25: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Consider schema validation instead of truthiness checks.

chatHistory, nodes, and edges are only checked for truthiness before being forwarded to the background task. Malformed shapes (wrong types, missing nested fields) will pass this check and surface as harder-to-diagnose failures inside the Trigger.dev task.

As per the feature spec, "Use Zod for request/task input validation" is called out as a general implementation note.

🤖 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 `@app/api/ai/spec/route.ts` around lines 18 - 25, Replace the truthiness checks
in the request handler around roomId, chatHistory, nodes, and edges with a Zod
schema that validates their required types and nested shapes before forwarding
data to the background task. Return a 400 response containing the validation
error for invalid input, while preserving the existing success flow for valid
requests.
prisma/models/taskrun.prisma (1)

2-8: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Redundant index on runId.

runId is the @id primary key and is already uniquely indexed. The explicit @@index([runId]) duplicates that index and only adds write/storage overhead. Drop it.

♻️ Proposed change
   createdAt DateTime `@default`(now())

-  @@index([runId])
   @@index([userId, projectId])
 }
🤖 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 `@prisma/models/taskrun.prisma` around lines 2 - 8, Remove the redundant
@@index([runId]) declaration from the TaskRun model, keeping the runId `@id`
primary-key definition and the composite @@index([userId, projectId]) unchanged.
components/editor/editor-shell.tsx (1)

59-76: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: CanvasLoader hardcodes bg-[#0d0d0f], duplicating the container styling in ErrorBoundary. Prefer a theme token (e.g. bg-base) for consistency with the rest of the shell, and consider extracting the shared full-screen container if this pattern grows.

🤖 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 `@components/editor/editor-shell.tsx` around lines 59 - 76, Update CanvasLoader
to replace the hardcoded bg-[`#0d0d0f`] class with the existing theme background
token, such as bg-base, matching the shell’s established styling and
ErrorBoundary container.
components/editor/ai-sidebar.tsx (2)

105-124: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

ActiveRunTracker default messages are design-specific but the component is reused for spec generation.

This component is rendered for both the design run (Line 641) and the spec run (Line 889). The hardcoded fallbacks — "Design generated successfully!" (Line 112) and "...during design generation." (Line 121) — produce misleading text when a spec run completes/fails (e.g. Spec generation failed: An unexpected error occurred during design generation.). Consider passing the task-specific default messages via props.

Also, run.output/run.metadata are cast with any (Lines 112, 120), which ESLint flags (@typescript-eslint/no-explicit-any); prefer a typed shape (e.g. { message?: string }) or unknown with a narrowing check.

🤖 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 `@components/editor/ai-sidebar.tsx` around lines 105 - 124, Update
ActiveRunTracker to accept task-specific completion and failure fallback
messages via props, and pass the appropriate design/spec messages from its
render sites instead of hardcoding design-specific text. Replace the any casts
on run.output and run.metadata with a typed message shape or unknown values
narrowed to objects containing an optional string message.

Source: Linters/SAST tools


230-236: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer inline error UI over alert().

handleGenerateSpec (Line 232), handleOpenPreview (Line 251), and the spec onFailed (Line 905) surface errors via alert(), which is jarring and inconsistent with the existing inline sendError pattern used in the Chat tab. Reusing an inline error state keeps error presentation consistent.

🤖 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 `@components/editor/ai-sidebar.tsx` around lines 230 - 236, Replace alert-based
error handling in handleGenerateSpec, handleOpenPreview, and the spec onFailed
callback with the existing inline sendError state pattern used by the Chat tab.
Preserve the current error messages and loading/status cleanup while rendering
failures through the sidebar’s inline error UI instead of browser alerts.
app/api/ai/design/route.ts (1)

7-55: 🚀 Performance & Scalability | 🔵 Trivial

No rate limiting on an endpoint that triggers paid AI generation.

Any collaborator with project access can call this repeatedly, fanning out unbounded Gemini calls and canvas mutations. Consider per-user/per-project rate limiting or debouncing at this entry point.

🤖 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 `@app/api/ai/design/route.ts` around lines 7 - 55, Add rate limiting at the
start of POST, after authentication and before triggering generateDesignTask,
using both userId and projectId as the limit scope. Reject requests exceeding
the configured limit with an appropriate 429 response, while preserving the
existing access checks and taskRun creation flow.
src/trigger/generate-design.ts (2)

373-395: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoff

All-or-nothing patch application after a long, sequential animation loop.

patchOps accumulates through the entire action loop (with 1s waits per move/add) and is only sent in a single PATCH call at the end. If the task is killed by maxDuration (300s) partway through a large plan, no changes are ever applied despite the Gemini call and presence broadcasts already having run — wasting the generation and giving no partial progress.

🤖 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/trigger/generate-design.ts` around lines 373 - 395, The design generation
flow should apply accumulated patch operations incrementally instead of waiting
until the entire sequential animation loop completes. Update the
action-processing logic that builds patchOps and the Liveblocks JSON Patch
request so completed batches are sent during the loop, while preserving
operation order and progress updates; ensure any remaining operations are
flushed before completion.

138-139: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Replace any with typed shapes for Liveblocks storage/action data.

ESLint flags no-explicit-any at Lines 138, 139, 160, 171, 232, and 326 (plus the catch clause at Line 405). Introducing small interfaces for the Liveblocks node/edge shape (and typing the catch as unknown) would remove these and catch shape drift at compile time.

Also applies to: 160-160, 171-171, 232-232, 326-326, 405-405

🤖 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/trigger/generate-design.ts` around lines 138 - 139, Replace the explicit
any annotations in generate-design.ts with small interfaces describing the
Liveblocks node and edge storage/action shapes, and apply those types
consistently to currentNodes, currentEdges, and the other flagged values at
lines 160, 171, 232, and 326. Type the catch variable at line 405 as unknown and
narrow it before use, preserving existing behavior while enabling compile-time
shape validation.

Source: Linters/SAST tools

src/trigger/generate-spec.ts (3)

1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Deprecated import path for @trigger.dev/sdk.

Package version is 4.5.3, but the code imports from the legacy @trigger.dev/sdk/v3 subpath. Per Trigger.dev's v4 migration guide, this path "still works, but will be removed in a future version", with the new path being @trigger.dev/sdk.

♻️ Proposed fix
-import { logger, task, metadata } from "`@trigger.dev/sdk/v3`";
+import { logger, task, metadata } from "`@trigger.dev/sdk`";
🤖 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/trigger/generate-spec.ts` at line 1, Update the import in
generate-spec.ts to use the current `@trigger.dev/sdk` package path instead of the
deprecated `@trigger.dev/sdk/v3` subpath, while preserving the existing logger,
task, and metadata imports.

50-61: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

No size bound on prompt inputs.

chatHistory, nodes, and edges are stringified into the prompt with no length/size limits. A large canvas or long conversation history can blow up token usage/cost and risks the model call failing or being truncated unpredictably.

🤖 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/trigger/generate-spec.ts` around lines 50 - 61, Limit the serialized
chatHistory, nodes, and edges included in the userPrompt construction to
explicit size bounds before interpolation. Preserve the existing context
sections and ensure truncation is deterministic and prevents oversized model
requests.

11-17: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Zod schema doesn't actually validate chatHistory/nodes/edges.

Using z.array(z.any()) accepts arbitrary content and defeats the purpose of schema validation — malformed or oversized payloads flow straight into the Gemini prompt and DB with no shape guarantees. Given types/tasks.ts is described elsewhere in this cohort as holding shared Zod schemas (e.g. for chat messages), consider reusing/defining typed schemas for these fields here instead of z.any().

🤖 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/trigger/generate-spec.ts` around lines 11 - 17, Replace the
z.array(z.any()) definitions in generateSpecPayloadSchema with typed shared Zod
schemas for chatHistory, nodes, and edges, reusing schemas from the shared task
types where available or defining appropriate object schemas locally. Preserve
the existing array fields while enforcing their expected item shapes before
payloads reach Gemini or the database.
app/api/projects/[projectId]/canvas/route.ts (1)

18-52: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

resolveProjectAccess is a local helper, not shared — sibling spec routes duplicate the same auth logic instead of reusing it.

app/api/projects/[projectId]/specs/[specId]/download/route.ts, .../specs/[specId]/route.ts, and .../specs/route.ts all re-implement the identical getCurrentUserIdentity() + checkProjectAccess() sequence inline rather than calling a shared helper. Since lib/project-access.ts already houses getCurrentUserIdentity/checkProjectAccess, consider moving resolveProjectAccess there too so all four routes share one implementation.

🤖 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 `@app/api/projects/`[projectId]/canvas/route.ts around lines 18 - 52, Move
resolveProjectAccess from the canvas route into lib/project-access.ts alongside
getCurrentUserIdentity and checkProjectAccess, then update the canvas and three
specs routes to import and reuse it. Remove their duplicated authentication and
project-access checks while preserving the existing unauthorized, forbidden, and
successful return behavior.
components/editor/collaborative-canvas.tsx (1)

419-426: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Component now hard-depends on ambient Liveblocks/ReactFlow context.

CollaborativeCanvas no longer sets up LiveblocksProvider/RoomProvider/ReactFlowProvider itself; InnerFlow uses useReactFlow(), useUpdateMyPresence(), and useLiveblocksFlow(), all of which will throw if rendered outside those providers. This is consistent with editor-shell.tsx (confirmed via graph evidence, which wraps CollaborativeCanvas in the required providers), so it's not currently broken, but the component's name/API no longer signals this required ambient context, making it easy to break for future call sites or tests.

Consider documenting the required provider context (e.g., a short JSDoc comment) or exporting a typed guard to make the contract explicit.

🤖 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 `@components/editor/collaborative-canvas.tsx` around lines 419 - 426, Document
the ambient provider contract for CollaborativeCanvas, explicitly stating that
callers must render it within the required Liveblocks/Room and ReactFlow
providers used by InnerFlow. Add a concise JSDoc comment adjacent to
CollaborativeCanvas without changing its current provider composition or
behavior.
🤖 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 @.agents/skills/trigger-agents/references/ai-tool.md:
- Around line 29-44: Update the generateText call inside weatherAgent’s
schemaTask run function to set maxSteps to a sufficient value such as 10,
allowing the model to continue after lookupWeather tool calls and return the
final answer text.

In @.agents/skills/trigger-agents/references/orchestration.md:
- Around line 192-198: Harden callExternalApi before invoking fetch: validate
payload.url as HTTPS, enforce the approved host allowlist, and reject loopback,
private, link-local, and cloud-metadata destinations. Add an
AbortController-based timeout to the fetch and ensure the signal is passed
through so hung requests release the rate-limited queue slot.

In @.agents/skills/trigger-agents/references/streaming.md:
- Around line 231-241: The streaming example incorrectly accesses streams.openai
even though STREAMS exposes progress and ai-output. Update the filtering and
fullText logic to use the defined ai-output stream key, preserving the existing
tool-call, tool-result, and text-delta handling.
- Around line 43-49: Update the streaming examples around progressStream.append
and aiOutputStream.append to await every append call, matching the promise-based
API. Correct useRealtimeStream examples to consume its exposed parts value
instead of data, and define or replace the undefined streams.openai reference
with the established stream symbol.

In @.agents/skills/trigger-agents/references/waitpoints.md:
- Around line 84-92: Update the approval endpoint’s POST handler to require an
authenticated session and authorize the caller before completing any token.
Validate tokenId, approved, and option against the expected approval contract,
reject invalid or unauthorized requests, and derive approvedBy from the
authenticated user rather than request-provided userId; pass only validated
values to wait.completeToken.

In @.agents/skills/trigger-agents/SKILL.md:
- Around line 207-214: Update the approval check in the evaluation flow after
generateText so it accepts only an exact normalized “APPROVED” response,
preventing phrases such as “NOT APPROVED” from passing. Preserve the existing
approved return payload and retry behavior for all other evaluation responses.
- Around line 84-98: Update the routing classification call in the trigger-agent
workflow to use the AI SDK structured-output API with routingSchema directly,
rather than parsing JSON.parse(routing.text). Extract model from the validated
structured result and preserve the existing model-selection behavior.

In @.agents/skills/trigger-config/references/config.md:
- Around line 81-97: Update the Python example to import pythonExtension from
`@trigger.dev/python/extension` and python from `@trigger.dev/python`, ensuring the
shown python.runInline and python.runScript usage is backed by the correct
imports.

In @.agents/skills/trigger-config/SKILL.md:
- Around line 102-117: Update the Python example to import pythonExtension from
`@trigger.dev/python/extension` and python from `@trigger.dev/python`, ensuring both
symbols used in the configuration and task example are explicitly imported from
their current package paths.

In @.agents/skills/trigger-setup/references/environment-setup.md:
- Around line 37-40: Update the Trigger.dev setup commands in the environment
setup documentation to use npx trigger.dev@latest for both dev and deploy
commands. Update the syncEnvVars usage to pass the async callback required by
the current API, preserving the existing environment-variable synchronization
behavior.
- Around line 71-90: Update the syncEnvVars configuration example to pass a real
asynchronous callback that returns EXTERNAL_API_KEY explicitly, and add a
separate note documenting that trigger dev reads local variables from .env or
the shell because syncing occurs only during deployment.

In `@app/api/ai/design/route.ts`:
- Around line 33-47: Persist the ownership record before triggering the
background task in the route handler, using a suitable pre-generated run
identifier for taskRun.create and preserving that identifier when calling
tasks.trigger. Ensure a failed database write prevents task execution, and
update the taskRun record afterward if Trigger.dev returns the definitive run
ID.

In `@app/api/ai/design/token/route.ts`:
- Around line 1-46: Before generating the token in POST, re-check the
authenticated user's current access to taskRun.projectId using the existing
project-access helper checkProjectAccess. Deny the request with the existing
Forbidden response unless current project access succeeds, while retaining the
existing TaskRun ownership validation and token scoping.

In `@app/api/ai/spec/route.ts`:
- Around line 36-51: Handle failures of prisma.taskRun.create immediately after
tasks.trigger in the spec route: log the persistence error and attempt to cancel
or otherwise clean up the already-started run using the available Trigger.dev
run identifier before returning the existing error response. Ensure cleanup
failures are handled without masking the original database error.

In `@app/api/ai/spec/token/route.ts`:
- Around line 33-39: Update the auth.createPublicToken call to include
expirationTime set to "1h" while preserving the existing scopes and runId
configuration.

In `@app/api/projects/`[projectId]/canvas/route.ts:
- Around line 18-52: Update resolveProjectAccess to return an explicit
discriminated union for success and error outcomes, using a consistent tag or
property that lets GET and PUT narrow the result before accessing errorResponse
or project. Preserve the existing Unauthorized and Forbidden responses and
successful project, userId, and emails values.

In `@lib/prisma.ts`:
- Around line 9-10: Update the global Prisma reset logic in lib/prisma.ts to run
only in the development/HMR path, and disconnect any existing
globalForPrisma.prisma client with $disconnect() before clearing its reference.
Preserve the existing production client-caching behavior and ensure the reset
safely handles an absent client.

In `@src/trigger/generate-design.ts`:
- Line 116: Update the logging in the design task startup flow to avoid logging
the full raw prompt from payload. Remove or sanitize the prompt data before
passing payload to logger.log, while preserving the startup message and any
non-sensitive metadata needed for diagnostics.
- Around line 130-153: Update the storage lookup flow around storageRes and
hasFlowRoot so any non-404 response failure aborts before patching /flow, rather
than continuing with empty currentNodes/currentEdges and hasFlowRoot false.
Preserve the existing 404 handling for rooms without storage, while propagating
or returning the failed lookup after logging.

---

Minor comments:
In @.agents/skills/trigger-agents/references/streaming.md:
- Around line 123-144: The Progress component’s useRealtimeStream example uses a
stale API and can parse an undefined value. Update useRealtimeStream to pass
progressStream as its first argument, consume the returned parts collection, and
render the waiting state when parts is empty before accessing its latest
element.

In @.agents/skills/trigger-agents/references/waitpoints.md:
- Around line 61-63: Choose one timeout policy for the Slack waitpoint example
and document it consistently in both the complete example and the tips. Update
the timeout handling around the result.ok check so the implementation matches
the documented guidance, including the corresponding sections around the timeout
tips.
- Line 40: Update the documentation around maxDuration in waitpoints.md to
describe it strictly as active compute time, including rewording the inline
comment for 600 and related tips. Clarify that the waitpoint token timeout
covers the human response window, without implying human delay extends
maxDuration.
- Around line 217-221: Update the review result handling after wait.forToken in
the approval flow: handle !review.ok in a separate branch first, returning its
failure feedback without accessing output, then handle rejected approval using
review.output.feedback after review.ok narrows the union.

In @.agents/skills/trigger-agents/SKILL.md:
- Around line 12-23: Update the fenced code block containing the
pattern-selection table in the skill documentation to declare the text language,
using the repository’s expected Markdown fence format so the MD040 warning is
resolved.

In @.agents/skills/trigger-config/references/config.md:
- Around line 338-346: Update the “Extensions only affect deployment” statement
in the Best Practices section to qualify that behavior: explain that extensions
generally affect deployment but some, including additionalFiles and the Python
development binary, also influence local development. Preserve the existing
guidance about using the external array.

In @.agents/skills/trigger-config/SKILL.md:
- Around line 231-240: Update the “Extensions only affect deployment” statement
in the Best Practices section to qualify it as applying only to deployment-only
extensions, preserving the exception for dev-mode settings such as
devPythonBinaryPath.

In @.agents/skills/trigger-setup/references/environment-setup.md:
- Around line 28-33: Specify the gitignore language on the fenced block in
environment-setup.md by changing its fence annotation to gitignore, while
preserving the existing .env entries.

In @.agents/skills/trigger-setup/references/project-structure.md:
- Around line 79-93: Add the missing import for task from `@trigger.dev/sdk` at
the start of the exported task example in the project structure documentation,
so the snippet is copy-paste complete while preserving the existing task
examples.
- Around line 5-13: Add an explicit text or plaintext language identifier to
each directory-tree fenced code block in project-structure.md, including the
trees near the shown project layout and the additionally referenced sections,
without changing their contents.

In @.agents/skills/trigger-setup/SKILL.md:
- Around line 90-98: Update the fenced project-tree example near the directory
structure to specify the text language, such as text or plaintext, immediately
after the opening fence. Keep the tree content unchanged.
- Around line 26-34: Update the CLI examples in the trigger setup skill to
invoke `trigger.dev@latest` instead of `trigger`, including the init, dev, and
deploy commands. Keep the existing command arguments and surrounding setup
instructions unchanged.

In `@components/editor/ai-sidebar.tsx`:
- Line 950: Update the instructional text near the “Generate Spec” label in the
AI sidebar to escape the literal quotation marks in JSX, satisfying
react/no-unescaped-entities while preserving the displayed wording.

In `@components/editor/canvas/custom-cursor.tsx`:
- Around line 41-49: Update the thinking spinner span in the custom cursor
component to use an explicit accessible role, such as role="status" for the “AI
thinking” announcement, or mark it aria-hidden="true" if the spinner is
decorative; do not leave aria-label on the bare span without a role.

In `@hooks/use-canvas-autosave.ts`:
- Around line 151-158: Update the keepalive gate in the unmount autosave flow to
measure the UTF-8 byte size of finalStateStr using
TextEncoder().encode(finalStateStr).length instead of the JavaScript string
length, while preserving the existing 64000-byte threshold and fetch behavior.

In `@src/trigger/generate-spec.ts`:
- Around line 26-27: Move generateSpecPayloadSchema.parse(payload) and the
parsed-field destructuring into the try block that handles generateSpec
execution, ensuring validation failures reach the existing failure handling and
set metadata status to "failed". Preserve the current parsed values and success
path for valid payloads.

---

Nitpick comments:
In @.agents/skills/trigger-agents/references/ai-tool.md:
- Around line 7-44: Replace the deprecated ai.tool usage in the weatherTool
example with the AI SDK tool() helper and execute:
ai.toolExecute(lookupWeather), importing tool from the AI SDK and ai from
`@trigger.dev/sdk/ai`. Update all remaining examples and tips in this document to
use the same pattern and remove references to ai.tool.

In @.agents/skills/trigger-config/references/config.md:
- Around line 125-135: Update the Lightpanda example using the lightpanda
extension to remove the floating version value: replace version "latest" with a
tested pinned version, or clearly label the snippet as a non-reproducible
development example. Keep the existing version and telemetry options otherwise
unchanged.

In `@app/api/ai/design/route.ts`:
- Around line 7-55: Add rate limiting at the start of POST, after authentication
and before triggering generateDesignTask, using both userId and projectId as the
limit scope. Reject requests exceeding the configured limit with an appropriate
429 response, while preserving the existing access checks and taskRun creation
flow.

In `@app/api/ai/spec/route.ts`:
- Around line 18-25: Replace the truthiness checks in the request handler around
roomId, chatHistory, nodes, and edges with a Zod schema that validates their
required types and nested shapes before forwarding data to the background task.
Return a 400 response containing the validation error for invalid input, while
preserving the existing success flow for valid requests.

In `@app/api/projects/`[projectId]/canvas/route.ts:
- Around line 18-52: Move resolveProjectAccess from the canvas route into
lib/project-access.ts alongside getCurrentUserIdentity and checkProjectAccess,
then update the canvas and three specs routes to import and reuse it. Remove
their duplicated authentication and project-access checks while preserving the
existing unauthorized, forbidden, and successful return behavior.

In `@components/editor/ai-sidebar.tsx`:
- Around line 105-124: Update ActiveRunTracker to accept task-specific
completion and failure fallback messages via props, and pass the appropriate
design/spec messages from its render sites instead of hardcoding design-specific
text. Replace the any casts on run.output and run.metadata with a typed message
shape or unknown values narrowed to objects containing an optional string
message.
- Around line 230-236: Replace alert-based error handling in handleGenerateSpec,
handleOpenPreview, and the spec onFailed callback with the existing inline
sendError state pattern used by the Chat tab. Preserve the current error
messages and loading/status cleanup while rendering failures through the
sidebar’s inline error UI instead of browser alerts.

In `@components/editor/collaborative-canvas.tsx`:
- Around line 419-426: Document the ambient provider contract for
CollaborativeCanvas, explicitly stating that callers must render it within the
required Liveblocks/Room and ReactFlow providers used by InnerFlow. Add a
concise JSDoc comment adjacent to CollaborativeCanvas without changing its
current provider composition or behavior.

In `@components/editor/editor-shell.tsx`:
- Around line 59-76: Update CanvasLoader to replace the hardcoded bg-[`#0d0d0f`]
class with the existing theme background token, such as bg-base, matching the
shell’s established styling and ErrorBoundary container.

In `@prisma/models/taskrun.prisma`:
- Around line 2-8: Remove the redundant @@index([runId]) declaration from the
TaskRun model, keeping the runId `@id` primary-key definition and the composite
@@index([userId, projectId]) unchanged.

In `@src/trigger/generate-design.ts`:
- Around line 373-395: The design generation flow should apply accumulated patch
operations incrementally instead of waiting until the entire sequential
animation loop completes. Update the action-processing logic that builds
patchOps and the Liveblocks JSON Patch request so completed batches are sent
during the loop, while preserving operation order and progress updates; ensure
any remaining operations are flushed before completion.
- Around line 138-139: Replace the explicit any annotations in
generate-design.ts with small interfaces describing the Liveblocks node and edge
storage/action shapes, and apply those types consistently to currentNodes,
currentEdges, and the other flagged values at lines 160, 171, 232, and 326. Type
the catch variable at line 405 as unknown and narrow it before use, preserving
existing behavior while enabling compile-time shape validation.

In `@src/trigger/generate-spec.ts`:
- Line 1: Update the import in generate-spec.ts to use the current
`@trigger.dev/sdk` package path instead of the deprecated `@trigger.dev/sdk/v3`
subpath, while preserving the existing logger, task, and metadata imports.
- Around line 50-61: Limit the serialized chatHistory, nodes, and edges included
in the userPrompt construction to explicit size bounds before interpolation.
Preserve the existing context sections and ensure truncation is deterministic
and prevents oversized model requests.
- Around line 11-17: Replace the z.array(z.any()) definitions in
generateSpecPayloadSchema with typed shared Zod schemas for chatHistory, nodes,
and edges, reusing schemas from the shared task types where available or
defining appropriate object schemas locally. Preserve the existing array fields
while enforcing their expected item shapes before payloads reach Gemini or the
database.
🪄 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 Plus

Run ID: 0acb6c3a-0f97-4196-9249-8fcc3f29d562

📥 Commits

Reviewing files that changed from the base of the PR and between cedaa05 and b248b6e.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (49)
  • .agents/skills/trigger-agents/SKILL.md
  • .agents/skills/trigger-agents/references/ai-tool.md
  • .agents/skills/trigger-agents/references/orchestration.md
  • .agents/skills/trigger-agents/references/streaming.md
  • .agents/skills/trigger-agents/references/waitpoints.md
  • .agents/skills/trigger-config/SKILL.md
  • .agents/skills/trigger-config/references/config.md
  • .agents/skills/trigger-setup/SKILL.md
  • .agents/skills/trigger-setup/references/environment-setup.md
  • .agents/skills/trigger-setup/references/project-structure.md
  • .gitignore
  • app/api/ai/design/route.ts
  • app/api/ai/design/token/route.ts
  • app/api/ai/spec/route.ts
  • app/api/ai/spec/token/route.ts
  • app/api/projects/[projectId]/canvas/route.ts
  • app/api/projects/[projectId]/specs/[specId]/download/route.ts
  • app/api/projects/[projectId]/specs/[specId]/route.ts
  • app/api/projects/[projectId]/specs/route.ts
  • app/globals.css
  • components/editor/ai-sidebar.tsx
  • components/editor/canvas/custom-cursor.tsx
  • components/editor/canvas/presence-avatars.tsx
  • components/editor/collaborative-canvas.tsx
  • components/editor/editor-navbar.tsx
  • components/editor/editor-shell.tsx
  • context/feature-specs/21-canvas-autosave.md
  • context/feature-specs/22-design-agent-api.md
  • context/feature-specs/23-design-agent-logic.md
  • context/feature-specs/24-ai-presence-state.md
  • context/feature-specs/25-sidebar-chat-feed.md
  • context/feature-specs/26-ai-chat-functional.md
  • context/feature-specs/27-spec-generation-flow.md
  • context/feature-specs/28-spec-persistence-download.md
  • context/feature-specs/29-spec-ui-integration.md
  • context/progress-tracker.md
  • hooks/use-canvas-autosave.ts
  • lib/prisma.ts
  • liveblocks.config.ts
  • package.json
  • prisma/models/project.prisma
  • prisma/models/projectspec.prisma
  • prisma/models/taskrun.prisma
  • skills-lock.json
  • src/trigger/generate-design.ts
  • src/trigger/generate-spec.ts
  • trigger.config.ts
  • tsconfig.json
  • types/tasks.ts

Comment on lines +29 to +44
// 3. Use with AI SDK
export const weatherAgent = schemaTask({
id: "weather-agent",
schema: z.object({ question: z.string() }),
run: async ({ question }) => {
const result = await generateText({
model: openai("gpt-4o"),
prompt: question,
tools: {
lookupWeather: weatherTool,
},
});

return { answer: result.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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'FILE: .agents/skills/trigger-agents/references/ai-tool.md\n'
wc -l .agents/skills/trigger-agents/references/ai-tool.md
sed -n '1,120p' .agents/skills/trigger-agents/references/ai-tool.md

printf '\nSEARCH: generateText / stopWhen / stepCountIs\n'
rg -n "generateText|stopWhen|stepCountIs|weatherAgent|weatherTool|ai-sdk" .agents -S

Repository: Unmixeddiplomacy/ghost-ai

Length of output: 4682


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "Checking AI SDK version references and generateText usage..."
rg -n "\"ai-sdk|`@ai-sdk`|ai-sdk-core|generateText|stopWhen|stepCountIs|maxSteps\"" package.json pnpm-lock.yaml yarn.lock package-lock.json .agents -S || true

Repository: Unmixeddiplomacy/ghost-ai

Length of output: 3286


🌐 Web query:

AI SDK generateText tools stopWhen stepCountIs default one step result.text empty incomplete after tool call docs

💡 Result:

In the Vercel AI SDK, if you encounter an empty or incomplete result when using tools with generateText or streamText, it is typically because the SDK defaults to a single generation step [1][2]. By default, the AI SDK does not automatically execute the model's tool calls and send those results back to the model for a follow-up response [3]. If the model chooses to call a tool, the generation ends after that initial step, often resulting in empty or incomplete text content because the model has not yet received the tool output to synthesize a final answer [1][3][2]. To enable the model to receive the tool results and generate a final response, you must configure multi-step execution using the stopWhen parameter [4][2]. Using stopWhen: stepCountIs(N) allows the SDK to automatically continue the generation process for up to N steps, passing tool results back to the model until a final text response is produced or the limit is reached [5][2]. Commonly, developers resolve the "empty result" issue by adding this parameter to their generateText or streamText configuration [1][6]: import { generateText, stepCountIs } from 'ai'; const result = await generateText({ model: yourModel, tools: yourTools, stopWhen: stepCountIs(5), // Adjust the step count as needed prompt: 'Your prompt here', }); If the issue persists despite setting stopWhen, ensure that your tool definitions are correct and that the model is actually receiving the tool output in the subsequent steps [1][6]. You can use onStepFinish to log and debug the execution process of each step to verify where the flow might be stopping [1].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '120,190p' .agents/skills/trigger-agents/references/ai-tool.md

Repository: Unmixeddiplomacy/ghost-ai

Length of output: 1733


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'package.json ai-related dependencies:\n'
sed -n '1,120p' package.json

printf '\npackage-lock ai dependency entries:\n'
rg -n '"node_modules/ai"|"\bai\b":|`@ai-sdk/openai`|`@ai-sdk/google`|stepCountIs|maxSteps' package-lock.json package.json -S

Repository: Unmixeddiplomacy/ghost-ai

Length of output: 2225


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'package.json ai-related dependencies:\n'
sed -n '1,140p' package.json

printf '\npackage-lock ai dependency entries:\n'
rg -n '"node_modules/ai"|"\bai\b":|`@ai-sdk/openai`|`@ai-sdk/google`|stepCountIs|maxSteps' package-lock.json package.json -S

Repository: Unmixeddiplomacy/ghost-ai

Length of output: 2225


Enable multi-step execution here. generateText only gets one step by default, so tool calls can leave result.text empty. Use maxSteps here (for example maxSteps: 10) so the model can continue after lookupWeather.

🤖 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 @.agents/skills/trigger-agents/references/ai-tool.md around lines 29 - 44,
Update the generateText call inside weatherAgent’s schemaTask run function to
set maxSteps to a sufficient value such as 10, allowing the model to continue
after lookupWeather tool calls and return the final answer text.

Source: MCP tools

Comment on lines +192 to +198
export const callExternalApi = task({
id: "call-external-api",
queue: rateLimitedQueue,
run: async (payload) => {
// Rate limited to 5 concurrent executions
return fetch(payload.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.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Harden the external fetch before presenting this as production code.

payload.url is unrestricted, so user-controlled input can create an SSRF sink. Enforce an HTTPS/host allowlist and block loopback, private, link-local, and metadata destinations. Also use an abort timeout so a hung request cannot occupy one of the five queue slots indefinitely.

🤖 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 @.agents/skills/trigger-agents/references/orchestration.md around lines 192 -
198, Harden callExternalApi before invoking fetch: validate payload.url as
HTTPS, enforce the approved host allowlist, and reject loopback, private,
link-local, and cloud-metadata destinations. Add an AbortController-based
timeout to the fetch and ensure the signal is passed through so hung requests
release the rate-limited queue slot.

Comment on lines +43 to +49
progressStream.append(
JSON.stringify({
current: i + 1,
total: items.length,
status: `Processing ${item.name}`,
})
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the referenced file and nearby context.
git ls-files .agents/skills/trigger-agents/references/streaming.md
wc -l .agents/skills/trigger-agents/references/streaming.md
sed -n '1,140p' .agents/skills/trigger-agents/references/streaming.md

# Find related stream examples and API references in the repo.
rg -n "useRealtimeStream|streams\.openai|progressStream\.append|aiOutputStream\.append|append\(" .agents -S

Repository: Unmixeddiplomacy/ghost-ai

Length of output: 4627


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the stream subscription and tool-call sections around the reported mismatches.
sed -n '120,250p' .agents/skills/trigger-agents/references/streaming.md

# Check whether the repo declares Trigger.dev SDK versions that constrain API shape.
rg -n "\"`@trigger.dev/sdk`\"|\"`@trigger.dev/react-hooks`\"|trigger.dev" package.json pnpm-lock.yaml yarn.lock package-lock.json . -S

# Look for any in-repo examples of awaiting append or using useRealtimeStream.
rg -n "await .*append\(|useRealtimeStream<|streams\.openai|tool-call|tool-result|text-delta" . -S

Repository: Unmixeddiplomacy/ghost-ai

Length of output: 16724


🌐 Web query:

Trigger.dev 4.5.3 streams define append Promise append API useRealtimeStream useRealtimeRunWithStreams TextStreamPart

💡 Result:

In Trigger.dev, streaming allows tasks to pipe continuous data (such as AI completions or progress updates) to frontend or backend consumers [1][2][3]. The Realtime Streams API includes several key components for defining, producing, and consuming these streams [4][5]. Key Concepts and APIs: streams.define: This method allows you to define a stream in one place, specifying the stream ID and the type of data chunks it handles, ensuring type safety when producing and consuming the stream [4]. streams.append and streams.writer: These methods are used within a task to produce data. - streams.append("streamId", data): Appends a single chunk of data to the specified stream [4]. - streams.writer("streamId", { execute: ({ write, merge }) => {... } }): A more robust approach for writing multiple chunks or merging existing streams (e.g., merging a ReadableStream) [4]. React Hooks for Consumption: - useRealtimeRunWithStreams(runId): A hook that subscribes to a specific run and provides access to its associated streams [5]. It is commonly used in React components to display streaming data [3][5]. - useRealtimeStream(stream, runId, options): A lower-level hook for subscribing to a specific stream [6]. Note that if you are appending object payloads, you may encounter issues where the data is coerced into "[object Object]"; a common workaround is to manually JSON-serialize objects before appending them and then parsing them on the client [6]. TextStreamPart: When subscribing to streams (e.g., via withStreams or specific stream hooks), the data received is typically structured as parts. While specific implementations may vary based on the data type, these parts often include metadata and the actual data chunk [5][6]. General Realtime Usage: While streaming provides continuous data, separate hooks like useRealtimeRun are used for tracking the high-level status of a task (e.g., status, progress percentage, completion time) [7][8]. Reliability: Trigger.dev's Realtime streams v2 implementation includes automatic resumption of failed reads and appends if a connection is lost, improving overall reliability [4].

Citations:


🌐 Web query:

site:trigger.dev OR site:github.com/triggerdotdev "append(" "streams" "Promise" "@trigger.dev/sdk" 4.5.3

💡 Result:

In the Trigger.dev SDK (version 4.5.3), the append functionality varies depending on whether you are using general task streams or the specialized AI Chat system. For general task streams, you use the append method on a defined stream to add a single chunk: import { task } from "@trigger.dev/sdk"; import { aiStream } from "@trigger.dev/sdk/ai"; // Example path // Inside your task await myStream.append(chunk); For the AI Chat system, append operations are handled by the transport layer when interacting with a chat session. The SDK provides high-level abstractions like AgentChat and TriggerChatTransport, which manage the underlying HTTP POST requests to the realtime API endpoint: POST /realtime/v1/sessions/:chatId/in/append When using the AgentChat SDK, you typically call.sendMessage, which returns a ChatStream. The transport automatically handles appending user messages to the session's durable.in stream on your behalf. If you are building a custom transport, you are responsible for POSTing the correct payload structure (e.g., {kind: "message", payload}) to that endpoint. Key technical notes: - The /in/append endpoint is authenticated using a session-scoped public access token (PAT). - In version 4.5+, the wire protocol was updated to a "Sessions-as-run-manager" architecture, where clients ship at most one new UIMessage per.in/append call, and the agent reconstructs history from snapshots. - Version 4.5.3 includes specific patches for TypeScript declaration issues related to chat types and ensures correct header propagation (x-trigger-branch) for chat agents deployed to preview branches.

Citations:


🌐 Web query:

site:trigger.dev site:github.com/triggerdotdev "@trigger.dev/react-hooks" "useRealtimeStream" "useRealtimeRunWithStreams" "append" "4.5.3"

💡 Result:

In @trigger.dev/react-hooks, useRealtimeStream is the current, recommended hook for consuming stream data from background tasks [1][2][3]. The older hook, useRealtimeRunWithStreams, is considered legacy [4][3]. Key details: 1. useRealtimeStream: This hook is designed for streaming continuous data such as AI tokens, file chunks, or progress updates [1][5]. It provides a simpler API with better type safety compared to its predecessor [4][2][3]. It is typically used in conjunction with streams defined in your task code using streams.define [2][5]. 2. useRealtimeRunWithStreams: This is an older, legacy hook that required manual type annotations and returned all streams within a single object [4][3]. It is recommended that new projects use the modern useRealtimeStream hook [3]. 3. Version 4.5.3: As of version 4.5.3 (released July 10, 2026), these hooks remain part of the @trigger.dev/react-hooks package [6][7]. The 4.5.3 release focused primarily on bug fixes (such as resolving a TypeScript TS2742 error) and server-side optimizations rather than changes to the React hooks API [6][8]. For projects utilizing these features, it is advised to follow the current documentation for the useRealtimeStream hook to ensure optimal performance and type safety [2].

Citations:


🌐 Web query:

site:github.com/triggerdotdev/trigger.dev "append(data" "Promise" "streams.define" "@trigger.dev/sdk"

💡 Result:

In the Trigger.dev SDK, streams.define and streams.append are part of the Realtime Streams API. You use streams.define to create a typed stream, and streams.append to add data to it [1]. 1. Define the stream: Use streams.define to create a reusable stream instance with a specific chunk type [1]. import { streams } from "@trigger.dev/sdk"; // Define a stream with a string chunk type const myStream = streams.define({ id: "my-stream", }); 2. Append data to the stream: You can append a single chunk of data to a stream defined with streams.define from inside a task [1]. // Append to the stream from inside a task await myStream.append("Hello, world!"); Note that if you append object payloads, the stream may receive the data as "[object Object]" unless properly handled or serialized [2]. For more complex streaming needs, you can also use streams.writer, which allows you to write multiple chunks or merge other streams [1]. Additionally, Trigger.dev has introduced input streams for bidirectional communication, which can be defined via streams.input and consumed using methods like.wait,.once, or.on [3][4].

Citations:


🌐 Web query:

site:trigger.dev "useRealtimeStream(" "stream:" "@trigger.dev/react-hooks" "data is array of emitted values"

💡 Result:

The useRealtimeStream hook from @trigger.dev/react-hooks returns an object that includes a property named parts, which is an array containing the accumulated stream chunks emitted by your task [1][2]. When using this hook, the parts array is automatically updated as new data chunks arrive from the stream [1][3]. You can map over this array in your React component to render the emitted values in real-time [1][2]. Key details about the hook's return value and usage: - parts: An array of type T (where T is the type of chunk defined in your stream) [1][3]. - error: An object containing any error information encountered during the stream subscription, or undefined if no error has occurred [1][2]. - Type Safety: If you pass a stream definition to the hook, parts will be automatically typed based on the stream's definition [1][3]. - Options: The hook accepts an options object where you can configure the accessToken, timeoutInSeconds, throttleInMs (to control re-render frequency), and an optional onData callback if you need to perform side effects whenever a new chunk arrives [1][2]. Example usage: const { parts, error } = useRealtimeStream(aiStream, runId, { accessToken: publicAccessToken, }); // Render the accumulated chunks return (

{parts.map((part, i) => ( {part}))}
);

Citations:


🌐 Web query:

site:trigger.dev/docs/tasks/streams "await myStream.append" "append(" "return type"

💡 Result:

In the context of the Trigger.dev framework, which utilizes the pattern of appending data to streams, the await myStream.append method returns a ManualWaitpointPromise [1]. This object is the same type as that returned by the wait.forToken function [1].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail
nl -ba .agents/skills/trigger-agents/references/streaming.md | sed -n '35,80p'

Repository: Unmixeddiplomacy/ghost-ai

Length of output: 203


Await every append() call and fix the stream examples. At .agents/skills/trigger-agents/references/streaming.md:43-75, both progressStream.append(...) and aiOutputStream.append(...) should be awaited; append() returns a promise in the current API. The later examples also read data from useRealtimeStream (it exposes parts) and reference streams.openai without defining it.

🤖 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 @.agents/skills/trigger-agents/references/streaming.md around lines 43 - 49,
Update the streaming examples around progressStream.append and
aiOutputStream.append to await every append call, matching the promise-based
API. Correct useRealtimeStream examples to consume its exposed parts value
instead of data, and define or replace the undefined streams.openai reference
with the established stream symbol.

Source: MCP tools

Comment on lines +231 to +241
```tsx
const { streams } = useRealtimeRunWithStreams<typeof aiTask, STREAMS>(runId, {
accessToken,
});

// streams.openai is TextStreamPart[]
const toolCalls = streams.openai?.filter(s => s.type === "tool-call") ?? [];
const toolResults = streams.openai?.filter(s => s.type === "tool-result") ?? [];
const textDeltas = streams.openai?.filter(s => s.type === "text-delta") ?? [];

const fullText = textDeltas.map(d => d.textDelta).join("");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- relevant section ---'
sed -n '210,255p' .agents/skills/trigger-agents/references/streaming.md

echo
echo '--- search STREAMS/openai/useRealtimeRunWithStreams ---'
rg -n "STREAMS|openai|useRealtimeRunWithStreams" .agents -S

Repository: Unmixeddiplomacy/ghost-ai

Length of output: 3839


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- file stats ---'
wc -l .agents/skills/trigger-agents/references/streaming.md

echo
echo '--- outline nearby context if possible ---'
python3 - <<'PY'
from pathlib import Path
p = Path('.agents/skills/trigger-agents/references/streaming.md')
for i, line in enumerate(p.read_text().splitlines(), 1):
    if 220 <= i <= 250:
        print(f"{i}: {line}")
PY

Repository: Unmixeddiplomacy/ghost-ai

Length of output: 1242


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- top of streaming.md ---'
sed -n '1,60p' .agents/skills/trigger-agents/references/streaming.md

echo
echo '--- around useRealtimeRunWithStreams earlier in file ---'
sed -n '140,190p' .agents/skills/trigger-agents/references/streaming.md

Repository: Unmixeddiplomacy/ghost-ai

Length of output: 2552


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- search for aiTask and any openai stream definitions ---'
rg -n "aiTask|id:\s*\"openai\"|streams\.openai|openaiStream|aiOutputStream" .agents -S

Repository: Unmixeddiplomacy/ghost-ai

Length of output: 1519


Use the defined AI output stream here. STREAMS only exposes progress and ai-output, so streams.openai doesn’t match the typed stream set. Switch this example to the actual stream key, or add/export a matching openai stream if that’s the intended API.

🤖 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 @.agents/skills/trigger-agents/references/streaming.md around lines 231 -
241, The streaming example incorrectly accesses streams.openai even though
STREAMS exposes progress and ai-output. Update the filtering and fullText logic
to use the defined ai-output stream key, preserving the existing tool-call,
tool-result, and text-delta handling.

Source: MCP tools

Comment on lines +84 to +92
// In your approval endpoint
export async function POST(request: Request) {
const { tokenId, approved, option, userId } = await request.json();

await wait.completeToken<ApprovalToken>(tokenId, {
approved,
selectedOption: option,
approvedBy: userId,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Authenticate and validate approval completions.

This endpoint trusts arbitrary tokenId, approved, option, and userId values. An unauthenticated caller could complete a waitpoint or impersonate an approver. Require authentication and authorization, validate the option, and derive approvedBy from the authenticated session.

🤖 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 @.agents/skills/trigger-agents/references/waitpoints.md around lines 84 - 92,
Update the approval endpoint’s POST handler to require an authenticated session
and authorize the caller before completing any token. Validate tokenId,
approved, and option against the expected approval contract, reject invalid or
unauthorized requests, and derive approvedBy from the authenticated user rather
than request-provided userId; pass only validated values to wait.completeToken.

Comment thread app/api/ai/spec/token/route.ts
Comment thread app/api/projects/[projectId]/canvas/route.ts Outdated
Comment thread lib/prisma.ts Outdated
Comment thread src/trigger/generate-design.ts Outdated
Comment thread src/trigger/generate-design.ts

@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: 1

🧹 Nitpick comments (2)
app/api/ai/design/route.ts (1)

42-55: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider using resolveProjectAccess for consistency.

This route still inlines getCurrentUserIdentity + checkProjectAccess while the three sibling specs routes were refactored to use the new resolveProjectAccess(projectId) helper. Consolidating here would keep the auth/access contract in one place going forward.

🤖 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 `@app/api/ai/design/route.ts` around lines 42 - 55, Update the project-access
flow in the design route to use the existing resolveProjectAccess(projectId)
helper instead of the inline checkProjectAccess path. Preserve the current
forbidden response when access is denied, and remove only the now-redundant
identity/access handling while leaving rate limiting unchanged.
src/trigger/generate-design.ts (1)

34-38: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace any in LiveblocksPatchOperation.value per ESLint.

Static analysis flags value?: any as @typescript-eslint/no-explicit-any. Since patch values are always JSON-serializable node/edge/position data, unknown (or a small union of the concrete value shapes used at each call site) would preserve type safety without weakening the interface.

♻️ Suggested fix
 interface LiveblocksPatchOperation {
   op: "add" | "remove" | "replace";
   path: string;
-  value?: any;
+  value?: unknown;
 }
🤖 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/trigger/generate-design.ts` around lines 34 - 38, Replace the explicit
any type on LiveblocksPatchOperation.value with unknown, or a focused union of
the JSON-serializable node, edge, and position shapes used by its call sites,
while preserving the optional property and existing patch operation behavior.

Source: Linters/SAST tools

🤖 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 `@app/api/ai/design/route.ts`:
- Around line 7-20: The local rateLimitMap and checkRateLimit implementation
cannot enforce limits across serverless replicas and retains expired keys
indefinitely. Replace this in-process limiter in the endpoint with the project’s
shared rate-limit store or Trigger.dev queue concurrency mechanism, preserving
the 5-requests-per-60-seconds behavior for each userId:projectId key and
removing the local map dependency.

---

Nitpick comments:
In `@app/api/ai/design/route.ts`:
- Around line 42-55: Update the project-access flow in the design route to use
the existing resolveProjectAccess(projectId) helper instead of the inline
checkProjectAccess path. Preserve the current forbidden response when access is
denied, and remove only the now-redundant identity/access handling while leaving
rate limiting unchanged.

In `@src/trigger/generate-design.ts`:
- Around line 34-38: Replace the explicit any type on
LiveblocksPatchOperation.value with unknown, or a focused union of the
JSON-serializable node, edge, and position shapes used by its call sites, while
preserving the optional property and existing patch operation behavior.
🪄 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 Plus

Run ID: 359cae6d-4d0a-45b9-a00d-076a07473508

📥 Commits

Reviewing files that changed from the base of the PR and between b248b6e and 79931d4.

📒 Files selected for processing (20)
  • .gitignore
  • app/api/ai/design/route.ts
  • app/api/ai/design/token/route.ts
  • app/api/ai/spec/route.ts
  • app/api/ai/spec/token/route.ts
  • app/api/projects/[projectId]/canvas/route.ts
  • app/api/projects/[projectId]/specs/[specId]/download/route.ts
  • app/api/projects/[projectId]/specs/[specId]/route.ts
  • app/api/projects/[projectId]/specs/route.ts
  • components/editor/ai-sidebar.tsx
  • components/editor/canvas/custom-cursor.tsx
  • components/editor/collaborative-canvas.tsx
  • components/editor/editor-shell.tsx
  • hooks/use-canvas-autosave.ts
  • lib/prisma.ts
  • lib/project-access.ts
  • package.json
  • prisma/models/taskrun.prisma
  • src/trigger/generate-design.ts
  • src/trigger/generate-spec.ts
💤 Files with no reviewable changes (1)
  • prisma/models/taskrun.prisma
🚧 Files skipped from review as they are similar to previous changes (11)
  • components/editor/canvas/custom-cursor.tsx
  • app/api/ai/design/token/route.ts
  • app/api/ai/spec/token/route.ts
  • app/api/projects/[projectId]/canvas/route.ts
  • hooks/use-canvas-autosave.ts
  • app/api/ai/spec/route.ts
  • components/editor/editor-shell.tsx
  • package.json
  • src/trigger/generate-spec.ts
  • components/editor/collaborative-canvas.tsx
  • components/editor/ai-sidebar.tsx

Comment thread app/api/ai/design/route.ts Outdated

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
app/api/ai/design/route.ts (4)

9-17: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Validate JSON field types before authorization.

Truthiness checks accept objects, arrays, and numbers. A malformed projectId then reaches resolveProjectAccess(projectId), whose contract requires a string, potentially producing a 500 instead of a 400. Validate the payload schema and require non-empty strings before continuing.

🤖 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 `@app/api/ai/design/route.ts` around lines 9 - 17, Update the payload
validation in the request handler around the destructured prompt, roomId, and
projectId fields to require each value to be a non-empty string, rejecting
malformed objects, arrays, and numbers with the existing 400 response before
calling resolveProjectAccess. Preserve the missing-field error behavior for
invalid or empty values.

27-55: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Make rate-limit reservation atomic.

The count and subsequent TaskRun.create are separate operations. Concurrent requests can all observe fewer than five runs and then create more than five records, bypassing the advertised limit. Use an atomic/shared limiter or a serializable transaction with retry/locking.

🤖 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 `@app/api/ai/design/route.ts` around lines 27 - 55, Make the rate-limit check
and TaskRun reservation in the route’s run-creation flow atomic, replacing the
separate taskRun.count and taskRun.create operations with a shared limiter or
serializable transaction that locks/retries as needed. Ensure concurrent
requests cannot reserve more than five runs within the sixty-second window while
preserving the existing 429 response and runId generation behavior.

74-87: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Do not return a run ID that failed to persist.

When taskRun.update fails, the response still returns run.id while the database row remains keyed by the pre-generated runId. Any downstream lookup using the returned ID can therefore fail to authorize, poll, or cancel the run. Retry/reconcile the update before reporting success, or return a tracking failure that triggers recovery.

#!/bin/bash
set -euo pipefail

rg -n --hidden --glob '!node_modules/**' --glob '!dist/**' \
  -e 'taskRun\.(findUnique|findFirst|update|delete)' \
  -e '\brunId\b' app lib src
🤖 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 `@app/api/ai/design/route.ts` around lines 74 - 87, Update the run-ID
reconciliation in the route’s taskRun.update block so the response never returns
run.id unless persisting the definitive ID succeeds. Retry or otherwise
reconcile the failed update, and if persistence still fails, return an
appropriate tracking failure response instead of the success response; keep the
existing successful update and return behavior unchanged.

46-72: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Make task creation resilient to failures and retries.

If tasks.trigger fails after the pre-created row is inserted, the catch block leaves an orphaned TaskRun that consumes rate-limit capacity and may represent a task that never started. Also, each HTTP attempt generates a new idempotency key, so a retry after an ambiguous Trigger.dev response can start duplicate design generation. Persist explicit task state, use a stable request idempotency key, and reconcile ambiguous outcomes instead of returning only a generic 500.

Also applies to: 88-92

🤖 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 `@app/api/ai/design/route.ts` around lines 46 - 72, Make the pre-created
TaskRun lifecycle resilient around tasks.trigger: persist an explicit
pending/started/failed state, and mark the row failed or reconcile it when
triggering throws instead of leaving an orphan. Derive the idempotency key from
a stable request identifier so retries reuse the same runId rather than
generating a new UUID. Handle ambiguous Trigger.dev outcomes by querying or
otherwise reconciling the existing run before returning an error, preserving the
existing successful response path.
🤖 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.

Outside diff comments:
In `@app/api/ai/design/route.ts`:
- Around line 9-17: Update the payload validation in the request handler around
the destructured prompt, roomId, and projectId fields to require each value to
be a non-empty string, rejecting malformed objects, arrays, and numbers with the
existing 400 response before calling resolveProjectAccess. Preserve the
missing-field error behavior for invalid or empty values.
- Around line 27-55: Make the rate-limit check and TaskRun reservation in the
route’s run-creation flow atomic, replacing the separate taskRun.count and
taskRun.create operations with a shared limiter or serializable transaction that
locks/retries as needed. Ensure concurrent requests cannot reserve more than
five runs within the sixty-second window while preserving the existing 429
response and runId generation behavior.
- Around line 74-87: Update the run-ID reconciliation in the route’s
taskRun.update block so the response never returns run.id unless persisting the
definitive ID succeeds. Retry or otherwise reconcile the failed update, and if
persistence still fails, return an appropriate tracking failure response instead
of the success response; keep the existing successful update and return behavior
unchanged.
- Around line 46-72: Make the pre-created TaskRun lifecycle resilient around
tasks.trigger: persist an explicit pending/started/failed state, and mark the
row failed or reconcile it when triggering throws instead of leaving an orphan.
Derive the idempotency key from a stable request identifier so retries reuse the
same runId rather than generating a new UUID. Handle ambiguous Trigger.dev
outcomes by querying or otherwise reconciling the existing run before returning
an error, preserving the existing successful response path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9abde4d5-8af5-40b4-a659-3cf8a30edd28

📥 Commits

Reviewing files that changed from the base of the PR and between 79931d4 and 09a202a.

📒 Files selected for processing (2)
  • app/api/ai/design/route.ts
  • src/trigger/generate-design.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/trigger/generate-design.ts

@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: 1

🧹 Nitpick comments (1)
app/api/ai/design/route.ts (1)

11-11: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Untyped catch blocks flagged by ESLint.

catch (err: any) and catch (txError: any) trigger @typescript-eslint/no-explicit-any. Prefer unknown with explicit narrowing (e.g., err instanceof Error, typeof err === "object" && err !== null && "code" in err).

Also applies to: 87-87

🤖 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 `@app/api/ai/design/route.ts` at line 11, Replace the explicit any types in the
catch clauses of the route handler with unknown, including the txError catch,
and narrow each value before accessing Error properties or transaction error
fields using appropriate runtime checks.

Source: Linters/SAST tools

🤖 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 `@app/api/ai/design/route.ts`:
- Around line 46-51: The hash-based runId in the request handling flow must
remain immutable as the deduplication key. Update the step 6
persistence/synchronization logic to store Trigger.dev’s run.id in its separate
dedicated column instead of overwriting runId, preserving the
requestHash-derived value from the runId assignment and preventing duplicate
TaskRun creation on retries.

---

Nitpick comments:
In `@app/api/ai/design/route.ts`:
- Line 11: Replace the explicit any types in the catch clauses of the route
handler with unknown, including the txError catch, and narrow each value before
accessing Error properties or transaction error fields using appropriate runtime
checks.
🪄 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 Plus

Run ID: d754e457-45eb-4879-b814-bff62da6a67a

📥 Commits

Reviewing files that changed from the base of the PR and between 09a202a and 0a4d92f.

📒 Files selected for processing (2)
  • app/api/ai/design/route.ts
  • prisma/models/taskrun.prisma
🚧 Files skipped from review as they are similar to previous changes (1)
  • prisma/models/taskrun.prisma

Comment thread app/api/ai/design/route.ts
@Unmixeddiplomacy
Unmixeddiplomacy merged commit 4b6824c into main Jul 13, 2026
1 check was pending
@coderabbitai coderabbitai Bot mentioned this pull request Jul 22, 2026
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