Skip to content

Conversation

@executiveusa
Copy link

@executiveusa executiveusa commented Dec 19, 2025

Description

Related Issues

Type of Change

  • Bug fix
  • New feature
  • Documentation
  • Refactor
  • Other (please describe):

Testing

Screenshots (if applicable)

Additional Notes


Important

Introduces Cynthia, an AI-driven design audit system with comprehensive API, database, UI, and documentation support.

  • Behavior:
    • Introduces Cynthia, an AI design auditor, evaluating UI across 13 axes with UDEC scoring.
    • Provides teaser and full audit reports with actionable fixes.
    • Supports URL, frame, and component audits.
  • API:
    • Adds auditRouter, buildSessionRouter, and previewRouter in routers/cynthia/ for audit management.
    • Implements create, get, list, getTeaser, getFull, updateStatus, saveResults, and unlock endpoints in audit.ts.
    • Handles build sessions and previews in build-session.ts and preview.ts.
  • Database:
    • Defines cynthia_audits, build_sessions, and preview_links tables with RLS in schema/cynthia/.
    • Supports audit status tracking and result storage.
  • UI:
    • Adds AuditTab component for audit creation and result display in right-panel/audit-tab/index.tsx.
    • Implements real-time status updates and teaser report display.
  • AI Integration:
    • Uses Claude 4.5 Sonnet for UDEC scoring in engine.ts.
    • Provides CynthiaAuditTool for AI-driven audit initiation.
  • Documentation:
    • Comprehensive guides in CYNTHIA.md, CYNTHIA_ARCHITECTURE.md, and CYNTHIA_IMPLEMENTATION_SUMMARY.md.
  • Testing:
    • Includes unit tests for audit engine in engine.test.ts.

This description was created by Ellipsis for 17476be. You can customize this summary. It will automatically update as commits are pushed.

Summary by CodeRabbit

  • New Features
    • Cynthia Design Auditor: in‑app audits with UDEC scores, teaser reports, unlock/paywall, fix‑packs, and apply-to-repo flow (PRs).
    • Build My Site: create/preview sessions with sharable preview links.
    • Credits & Plans: user credit balances, plans, and credit consumption for paid actions.
  • Project UI
    • Landing refresh: new hero input, teaser demo, synthia sections, preview page, audit tab, apply‑fix dialog and status panel.
  • APIs & Services
    • New backend APIs, background workers/queues, and CI monitoring for audits and apply pipeline.
  • Documentation
    • Extensive Cynthia docs and flow guides.
  • Tests
    • Audit engine unit tests.

✏️ Tip: You can customize this high-level summary in your review settings.

Copilot AI and others added 11 commits December 15, 2025 20:25
Co-authored-by: executiveusa <191772270+executiveusa@users.noreply.github.com>
Co-authored-by: executiveusa <191772270+executiveusa@users.noreply.github.com>
Co-authored-by: executiveusa <191772270+executiveusa@users.noreply.github.com>
Co-authored-by: executiveusa <191772270+executiveusa@users.noreply.github.com>
Co-authored-by: executiveusa <191772270+executiveusa@users.noreply.github.com>
Co-authored-by: executiveusa <191772270+executiveusa@users.noreply.github.com>
…features

Implement Cynthia v3.0: AI Design Auditor with UDEC Scoring
Transform homepage from Onlook branding to SYNTHIA 3.0 "Build My Site" viral wedge.
Replaces "Cursor for Designers" positioning with credit-bureau-style design audit approach.

Changes:
- Hero: Replace headline/subhead with "Stop shipping ugly" messaging and add URL/idea input field with dual CTAs
- Sections: Replace old landing sections with 6 new storytelling sections (Demo, Credit Bureau, Fix Packs, Design System, Deploy, White Label)
- Demo: Add static teaser report component with bureau-style score display, severity badges, and paywall preview
- i18n: Add complete bilingual EN/ES support for all landing page copy via next-intl
- Motion: Implement scroll-based reveals, staggered animations, and reduced-motion support
- Metadata: Update title, description, and OG tags from Onlook to SYNTHIA 3.0 branding
- Docs: Add comprehensive build-my-site-flow.md with 3 Mermaid diagrams for user flow, system orchestration, and paywall gating

Preserves Onlook's premium dark theme aesthetic while removing gradients/neon per ARCHITECT requirements.
Static demo data will be wired to real audit API in Phase 3.
Database Layer:
- Add Drizzle schemas for build_sessions and preview_links tables
- Create comprehensive SQL migration with RLS policies
- Support anonymous sessions with optional user claiming
- Unguessable preview slugs for viral sharing (min 8 chars)

API Layer:
- Add buildSession router (create, getTeaser, list endpoints)
- Add preview router (getBySlug endpoint)
- Public procedures for anonymous access
- Static teaser data (Phase 2 scope)

UI Layer:
- Wire hero CTA to buildSession.create mutation
- Add /preview/[slug] route for shareable previews
- Display teaser score (68/100) and sample issues
- Add share button (copy-to-clipboard)
- Add unlock CTA stub (paywall placeholder)

Security:
- RLS policies: Public insert on sessions, owner select only
- Preview links publicly readable by slug only
- No enumeration attacks (no public listing)
- Anonymous sessions can be claimed by authenticated users

Phase 2 complete. Ready for ARCHITECT review.
Database Layer:
- Add audit_id and audit_status columns to build_sessions
- Create build_session_audit_status enum (pending/running/completed/failed)
- Add relation from build_sessions to cynthia_audits
- Migration generated (NOT applied per instructions)

Background Processing:
- Create build-session-processor.ts wrapping existing audit processor
- Wire buildSession.create to trigger real Cynthia audits
- Derive teaser from real audit results (overallScore, top 3 issues)
- System project/user IDs for anonymous builds (placeholder UUIDs)
- Async processing via setTimeout (non-blocking)

API Layer:
- Update buildSession.create: Remove static data, start real audit
- Update buildSession.getTeaser: Return progress or real teaser data
- Add buildSession.claim: Attach userId to anonymous sessions
- Add audit.unlock: Stub endpoint (always returns false + upgrade message)

UI Layer:
- Update preview page with 3 states: loading, progress, completed
- Poll every 3s while audit runs (refetchInterval)
- Display progress bar with status text during audit
- Show real teaser data when completed (score + top 3 issues)
- Wire unlock CTA to audit.unlock stub
- Handle failed audit state

Security:
- Preview never exposes fullIssues (only teaserIssues)
- Unlock stub denies all requests (Phase 4 will check entitlement)
- Anonymous claim validates session ownership
- Real audit data, no fabrication

Phase 3 complete. Ready for ARCHITECT review.
@vercel
Copy link

vercel bot commented Dec 19, 2025

@claude is attempting to deploy a commit to the Onlook Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link

coderabbitai bot commented Dec 19, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

Adds Cynthia v3.0: DB migrations and Drizzle schemas for audits, build sessions, previews, credits, fix-packs, and apply-runs; new tRPC routers, background processors/queues, an AI audit engine and prompt, client landing/preview/audit UI, credit/fix-pack/apply pipelines, workers, docs, and tests.

Changes

Cohort / File(s) Summary
Database migrations
apps/backend/supabase/migrations/*add_*.sql, apps/backend/supabase/migrations/2025_12_27_add_apply_runs.sql, apps/backend/supabase/migrations/2025_12_19_fix_rls_with_security_definer.sql
New enums and tables (cynthia_audits, build_sessions, preview_links, credit_balances, fix_packs, apply_runs), indexes, RLS policies, SECURITY DEFINER functions (ensure_credit_balance, consume_credit, create_fix_pack_record, create_apply_run_record, update_apply_run_status), monthly reset function, and comments.
Drizzle DB schemas (Cynthia)
packages/db/src/schema/cynthia/*, packages/db/src/schema/index.ts
New Drizzle schemas and enums, relations, Zod insert/update schemas, types and barrel re-exports for audits, build-sessions, preview-links, credit-balances, fix-packs, apply-runs; RLS enabled.
tRPC routers & processors
apps/web/client/src/server/api/routers/cynthia/*, apps/web/client/src/server/api/root.ts, apps/web/client/src/server/api/routers/index.ts
New routers: audit, buildSession, preview, fixPack, credits, applyRun; procedures for create/get/list/getTeaser/getFull/saveResults/updateStatus/unlock/claim/generate/list/get/apply; processor and build-session-processor wire audit lifecycle and background queuing.
AI audit engine, prompts & tools
packages/ai/src/audit/*, packages/ai/src/prompt/constants/*, packages/ai/src/agents/root.ts, packages/ai/src/tools/*, packages/ai/src/index.ts
New Cynthia system prompt, audit engine (runAudit, streamAudit), example utilities, ChatType.AUDIT routing, CynthiaAuditTool class, tool registration, and audit exports.
Models & types
packages/models/src/cynthia/*, packages/models/src/chat/type.ts, packages/models/src/index.ts
New UDEC axes, DesignIssue/IssueFix types, CynthiaReport/AuditInput models, AuditStatus enum, LLM routing (TaskRoute/ModelTier/TASK_ROUTING), ChatType.AUDIT addition, and barrel exports.
Build session & preview flow
apps/web/client/src/server/api/routers/cynthia/build-session.ts, apps/web/client/src/server/api/routers/cynthia/preview.ts, apps/web/client/src/server/api/routers/cynthia/build-session-processor.ts
Build session creation (anon/auth), preview slug generation, teaser retrieval with progress/paywall, linking build sessions to audits, async audit start and processor that derives teaser data and updates session status.
Queueing, workers & background services
apps/web/client/src/server/api/services/queue-service.ts, apps/web/client/src/server/api/workers/*, apps/web/client/src/server/workers/start-workers.ts, apps/web/client/package.json, apps/web/client/src/env.ts
Introduces BullMQ/Redis queues, workers (apply/monitor), startup script for workers, queue helpers, env vars for REDIS, new package deps and "workers" script.
Apply pipeline & CI monitoring
apps/web/client/src/server/api/services/apply-pipeline-service.ts, apps/web/client/src/server/api/services/ci-monitoring-service.ts, apps/web/client/src/server/api/workers/monitor-worker.ts
End-to-end apply pipeline: patch application, PR creation, CI check polling, repair trigger; applies fix packs to repos and updates apply-run status.
Fix packs & monetization
apps/web/client/src/server/api/routers/cynthia/fix-pack.ts, apps/web/client/src/server/api/services/credit-service.ts, apps/web/client/src/server/api/routers/cynthia/credits.ts, packages/db/src/schema/cynthia/fix-pack.ts, packages/db/src/schema/cynthia/credit-balance.ts
Fix-pack generation/list/get/apply flows, credit balance model and service (get/create/check/consume/updatePlan/reset), credits router, and enforcement via SECURITY DEFINER functions and RLS.
Client UI: landing, hero, preview, audit, apply UI
apps/web/client/messages/*, apps/web/client/src/app/_components/hero/index.tsx, apps/web/client/src/app/_components/landing-page/*, apps/web/client/src/app/_components/landing-page/teaser-demo-section.tsx, apps/web/client/src/app/preview/[slug]/page.tsx, apps/web/client/src/app/project/[id]/_components/right-panel/audit-tab/index.tsx, apps/web/client/src/app/_components/apply-fix-pack/*, apps/web/client/src/app/page.tsx, apps/web/client/src/app/layout.tsx
Localized landing content and Synthia branding; hero input flow creating build sessions; new landing sections and TeaserDemoSection; preview page polling build sessions/audits; AuditTab inside project; ApplyFixPack UI components and status panel; metadata/title updates.
Background workflows & OpenHands repair
.github/workflows/openhands-repair.yml, apps/web/client/src/server/api/services/queue-service.ts
New GitHub Actions workflow to run OpenHands repair agent on labeled PRs and queueing support for repair jobs via queues.
Docs & tests
docs/*CYNTHIA*.md, docs/build-my-site-flow.md, docs/apply-fix-pack-pipeline.md, docs/background-jobs.md, README.md, packages/ai/test/audit/engine.test.ts
New comprehensive Cynthia docs (architecture, implementation, flows), build-my-site flow, apply-pipeline docs, background-jobs notes, README bullet, and unit tests for audit engine schemas and report structure.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor User
    participant Hero as Hero UI
    participant API as tRPC buildSession
    participant DB as Database
    participant Queue as Queue Service (BullMQ / setTimeout)
    participant Worker as Bg Processor / Audit Worker
    participant Engine as Cynthia Audit Engine
    participant Preview as Preview Page

    User->>Hero: submit URL/idea
    Hero->>API: buildSession.create(input)
    API->>DB: insert build_session + preview_link (audit_status=pending)
    API->>Queue: startBuildSessionAudit (enqueue/async)
    API-->>Hero: {buildSessionId, previewSlug}
    Hero->>Preview: navigate to /preview/{slug}

    Preview->>API: buildSession.getTeaser(slug)
    API-->>Preview: {auditStatus: pending, progress: 0}
    Preview->>Preview: poll periodically...

    par Background processing
        Queue->>Worker: deliver audit job
        Worker->>DB: update build_session.audit_status -> running
        Worker->>Engine: runAudit(target)
        Engine-->>Worker: {teaserSummary, teaserScore, issues}
        Worker->>DB: update build_session (teaserSummary, teaserScore, audit_status=completed)
    end

    Preview->>API: buildSession.getTeaser(slug) (poll)
    API-->>Preview: {auditStatus: completed, teaserSummary, teaserScore}
    Preview->>User: render teaser, issues, unlock CTA
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • Kitenite
  • saddlepaddle

Poem

🐇 I hop through pixels, score each line,
Thirteen axes tune the interface fine.
Teasers show issues, fix packs take flight,
I nudge Synthia to make the UI right.
Tiny paws applaud — audits done tonight.

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is largely a template with placeholders unfilled; the core changes are only conveyed via auto-generated Ellipsis summary in a collapsed section, making the description incomplete and difficult to parse. Replace template placeholders with actual details: add clear description section, link related issues, check appropriate change type, describe testing steps, and remove or minimize reliance on auto-generated summaries.
Docstring Coverage ⚠️ Warning Docstring coverage is 73.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'Claude/build website builder 7p1p y' is vague and uses branch naming convention; it does not clearly communicate the main change (introducing Cynthia design auditor system). Revise title to be descriptive and business-focused, e.g., 'Add Cynthia AI design audit system with scoring and fix packs' or similar.
✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

export const metadata: Metadata = {
title: 'Onlook – Cursor for Designers',
description: 'The power of Cursor for your own website. Onlook lets you edit your React website and write your changes back to code in real-time. Iterate and experiment with AI.',
title: 'Synthia 3.0 – AI Design Auditor & Front-End Builder',
Copy link
Contributor

Choose a reason for hiding this comment

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

Typographical consistency issue: The metadata title uses 'Synthia 3.0' while the descriptions refer to 'Cynthia'. Please ensure the product name is spelled consistently throughout.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 251 to 255
unlock: publicProcedure
.input(z.object({ buildSessionId: z.string().uuid() }))
.mutation(async ({ ctx, input }) => {
// Phase 3 stub: Always return false (not allowed)
// Phase 4 will implement:

Choose a reason for hiding this comment

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

P1 Badge Import publicProcedure for audit unlock route

auditRouter.unlock uses publicProcedure, but the module only imports createTRPCRouter and protectedProcedure, so TypeScript will fail to compile this file and the unlock endpoint cannot be built. Add the missing import from ../../trpc before using publicProcedure.

Useful? React with 👍 / 👎.

Comment on lines +21 to +24
-- Add audit fields to build_sessions
ALTER TABLE build_sessions
ADD COLUMN audit_id UUID REFERENCES cynthia_audits(id) ON DELETE SET NULL ON UPDATE CASCADE,
ADD COLUMN audit_status build_session_audit_status DEFAULT 'pending';

Choose a reason for hiding this comment

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

P1 Badge Migration references missing cynthia_audits table

This migration adds audit_id with a foreign key to cynthia_audits, but no migration creates that table (rg over apps/backend/supabase/migrations finds only this reference), so applying this script will fail with a missing relation error and block the migration stack. The table or its creation migration needs to be added before introducing the FK.

Useful? React with 👍 / 👎.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 20

Note

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

🟡 Minor comments (7)
apps/web/client/messages/es.json-137-137 (1)

137-137: Change "Synthia" to "Cynthia" in welcome.title for consistency with branding.

The welcome title uses "Synthia" in both English (en.json:137) and Spanish (es.json:137), but all other references throughout both locale files consistently use "Cynthia" (e.g., "Cynthia Score", "Cynthia Design Audit", "Rebrand Cynthia"). Update both locale files to use "Cynthia" to maintain consistent branding.

apps/web/client/src/app/layout.tsx-22-23 (1)

22-23: Verify naming consistency: "Cynthia" vs "Synthia".

The title uses "Synthia 3.0" while the description uses "Cynthia". Please ensure consistent branding throughout the metadata.

apps/web/client/messages/en.json-137-137 (1)

137-137: Inconsistent naming: "Synthia" vs "Cynthia".

The welcome title says "Welcome to Synthia" but all other references in this file (lines 96, 98, 410, 429) and throughout the PR use "Cynthia". This appears to be a typo.

🔎 Proposed fix
-        "title": "Welcome to Synthia",
+        "title": "Welcome to Cynthia",
apps/web/client/src/app/_components/landing-page/synthia-sections.tsx-189-203 (1)

189-203: Replace hardcoded feature strings with translations.

Similar to the DesignSystemSection, these feature strings ('Direct GitHub commits', 'Automated pull requests', 'One-click deploy') are hardcoded and should use translations.

🔎 Proposed fix
 <div className="space-y-4">
-    {['Direct GitHub commits', 'Automated pull requests', 'One-click deploy'].map(
-        (feature, index) => (
+    {(['feature1', 'feature2', 'feature3'] as const).map(
+        (key, index) => (
             <MotionDiv
-                key={feature}
+                key={key}
                 initial={{ opacity: 0, x: 20 }}
                 whileInView={{ opacity: 1, x: 0 }}
                 viewport={{ once: true }}
                 transition={{ duration: 0.5, delay: index * 0.1 }}
                 className="flex items-center gap-3"
             >
                 <Icons.Check className="h-5 w-5 text-primary flex-shrink-0" />
-                <span className="text-lg">{feature}</span>
+                <span className="text-lg">{t(key)}</span>
             </MotionDiv>
         ),
     )}
 </div>

As per coding guidelines, avoid hardcoded user-facing text in apps/web/client/src/**/*.{ts,tsx}.

Committable suggestion skipped: line range outside the PR's diff.

apps/web/client/src/app/_components/landing-page/synthia-sections.tsx-231-251 (1)

231-251: Replace hardcoded feature data with translations.

The feature objects at lines 232-234 contain hardcoded English strings ('Custom Branding', 'Your logo, your colors', etc.) that should be translated.

🔎 Proposed fix
 <div className="grid grid-cols-1 md:grid-cols-3 gap-8 mt-12">
     {[
-        { icon: Icons.Palette, title: 'Custom Branding', desc: 'Your logo, your colors' },
-        { icon: Icons.Globe, title: 'Custom Domain', desc: 'audit.youragency.com' },
-        { icon: Icons.Users, title: 'Client Portals', desc: 'Branded client access' },
+        { icon: Icons.Palette, titleKey: 'feature1Title', descKey: 'feature1Desc' },
+        { icon: Icons.Globe, titleKey: 'feature2Title', descKey: 'feature2Desc' },
+        { icon: Icons.Users, titleKey: 'feature3Title', descKey: 'feature3Desc' },
     ].map((feature, index) => {
         const Icon = feature.icon;
         return (
             <MotionDiv
-                key={feature.title}
+                key={feature.titleKey}
                 initial={{ opacity: 0, y: 20 }}
                 whileInView={{ opacity: 1, y: 0 }}
                 viewport={{ once: true }}
                 transition={{ duration: 0.5, delay: index * 0.1 }}
                 className="p-8 rounded-xl border bg-background-secondary/50 hover:bg-background-secondary transition-colors"
             >
                 <Icon className="h-12 w-12 text-primary mx-auto mb-4" />
-                <h3 className="text-xl font-semibold mb-2">{feature.title}</h3>
-                <p className="text-foreground-secondary">{feature.desc}</p>
+                <h3 className="text-xl font-semibold mb-2">{t(feature.titleKey)}</h3>
+                <p className="text-foreground-secondary">{t(feature.descKey)}</p>
             </MotionDiv>
         );
     })}
 </div>

Based on learnings, use next-intl messages/hooks for all user-facing text.

Committable suggestion skipped: line range outside the PR's diff.

apps/web/client/src/app/_components/landing-page/synthia-sections.tsx-125-139 (1)

125-139: Replace hardcoded feature strings with translations.

The feature strings ('Token extraction', 'Violation detection', 'Regression prevention') are hardcoded in English, violating the coding guideline to avoid hardcoded user-facing text.

🔎 Proposed fix

Update the i18n messages file to include these features, then use translated keys:

 <div className="space-y-4">
-    {['Token extraction', 'Violation detection', 'Regression prevention'].map(
-        (feature, index) => (
+    {(['feature1', 'feature2', 'feature3'] as const).map(
+        (key, index) => (
             <MotionDiv
-                key={feature}
+                key={key}
                 initial={{ opacity: 0, x: -20 }}
                 whileInView={{ opacity: 1, x: 0 }}
                 viewport={{ once: true }}
                 transition={{ duration: 0.5, delay: index * 0.1 }}
                 className="flex items-center gap-3"
             >
                 <Icons.Check className="h-5 w-5 text-primary flex-shrink-0" />
-                <span className="text-lg">{feature}</span>
+                <span className="text-lg">{t(key)}</span>
             </MotionDiv>
         ),
     )}
 </div>

Add to your messages file (e.g., messages/en.json):

{
  "landing": {
    "sections": {
      "designSystem": {
        "feature1": "Token extraction",
        "feature2": "Violation detection",
        "feature3": "Regression prevention"
      }
    }
  }
}

Based on learnings, apps/web/client/src/**/*.{ts,tsx} should avoid hardcoded user-facing text and use next-intl messages/hooks instead.

Committable suggestion skipped: line range outside the PR's diff.

docs/build-my-site-flow.md-295-302 (1)

295-302: Add language identifier to fenced code block.

The code block showing the message files structure should have a language identifier (e.g., plaintext or text) to satisfy markdown linting rules.

🔎 Proposed fix
-```
+```plaintext
 apps/web/client/messages/
   ├── en.json  (English - authoritative)
   ├── es.json  (Spanish - ARCHITECT-approved copy)
   ├── ja.json  (Japanese - existing)
   ├── ko.json  (Korean - existing)
   └── zh.json  (Chinese - existing)
</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (21)</summary><blockquote>

<details>
<summary>packages/ai/src/prompt/constants/cynthia.ts (1)</summary><blockquote>

`145-147`: **Consider simplifying the getter function.**

The `getCynthiaSystemPrompt()` function currently just returns the constant without any transformation. Unless you plan to add dynamic behavior (e.g., parameterization, context injection), you could simplify by directly exporting the constant.



<details>
<summary>🔎 Optional simplification</summary>

If no dynamic behavior is needed, consumers can import the constant directly:

```diff
-export const getCynthiaSystemPrompt = (): string => {
-    return CYNTHIA_SYSTEM_PROMPT;
-};

Then update imports from:

import { getCynthiaSystemPrompt } from '@onlook/ai';
const prompt = getCynthiaSystemPrompt();

To:

import { CYNTHIA_SYSTEM_PROMPT } from '@onlook/ai';
const prompt = CYNTHIA_SYSTEM_PROMPT;

However, keeping the getter provides flexibility for future enhancements without breaking changes.

docs/CYNTHIA_ARCHITECTURE.md (1)

209-247: Add language specifier to fenced code block.

The code block starting at line 215 is missing a language identifier, which affects syntax highlighting and linting.

🔎 Proposed fix
 ### Core Types Hierarchy
 
-```
+```text
 ┌─────────────────────────────────────┐
 │         AuditInput                  │
packages/ai/test/audit/engine.test.ts (1)

97-188: Consider adding engine integration tests.

These tests validate data structure contracts, which is valuable. However, the test suite is named "Cynthia Audit Engine" but doesn't exercise runAudit or streamAudit from packages/ai/src/audit/engine.ts. Consider adding integration tests with mocked LLM responses to verify the engine's prompt-building and response-parsing logic.

packages/db/src/schema/cynthia/preview-link.ts (1)

15-20: Consider adding an index on buildSessionId for FK lookups.

The foreign key relationship to buildSessions will likely be queried when fetching preview links for a session. Adding an index would improve lookup performance, especially as the table grows.

🔎 Proposed addition
 (table) => ({
     // Unique slug constraint
     slugUnique: unique().on(table.slug),
+    // Index for foreign key lookups
+    buildSessionIdIdx: index('idx_preview_links_build_session_id').on(table.buildSessionId),
 }),

Note: You'll also need to import index from drizzle-orm/pg-core.

docs/CYNTHIA.md (1)

215-220: Add language identifier to code block.

The code block at line 215 is missing a language identifier, which reduces syntax highlighting and clarity.

🔎 Proposed fix
-```
+```plaintext
 "Can you audit the design of this page?"
 "Run a Cynthia audit on my landing page"
 "What design issues do you see?"

</details>

</blockquote></details>
<details>
<summary>apps/web/client/src/server/api/routers/cynthia/preview.ts (1)</summary><blockquote>

`60-64`: **Document the Phase 2 paywall behavior.**

The hardcoded `locked: true` is marked as Phase 2 behavior but may benefit from additional context about when it will be replaced with dynamic logic.



Consider adding a comment explaining the roadmap:

```diff
 return {
     buildSession,
     previewSlug: previewLink.slug,
-    locked: true, // Always locked in Phase 2 (paywall)
+    locked: true, // Phase 2: Always locked (paywall). Phase 3: Check user subscription status
 };
apps/web/client/src/app/_components/landing-page/teaser-demo-section.tsx (3)

11-40: Move hardcoded demo data strings to i18n message files.

The DEMO_DATA object contains hardcoded English strings (titles, descriptions, reasons, impacts) that should be externalized to next-intl message files to support internationalization.

Based on coding guidelines and learnings.

🔎 Suggested approach

Move the static issue data to message files:

// In messages/en.json
{
  "landing.demo.issues": {
    "0": {
      "title": "Insufficient color contrast on CTA button",
      "description": "Primary CTA button has 3.2:1 contrast ratio",
      "reason": "Fails WCAG AA (4.5:1) - reduces visibility for users with low vision",
      "impact": "Lost conversions, accessibility compliance risk"
    },
    // ... other issues
  }
}

Then reference them in the component:

const t = useTranslations('landing.demo');
// Access via t('issues.0.title'), etc.

175-175: Externalize hardcoded text to i18n messages.

The placeholder fix description is hardcoded in English and should be moved to a translation key.

Based on coding guidelines.


181-181: Externalize hardcoded text to i18n messages.

The "Unlock to see fix" text is hardcoded in English. Use a translation key instead, e.g., tCynthia('issues.unlockFix').

Based on coding guidelines.

apps/web/client/src/app/project/[id]/_components/right-panel/audit-tab/index.tsx (1)

21-24: Consider stopping poll when audit reaches terminal state.

The refetchInterval: 2000 will continue polling indefinitely even after the audit completes or fails. Consider conditionally disabling polling when the status is COMPLETED or FAILED to reduce unnecessary API calls.

🔎 Suggested improvement
 const { data: audit, refetch } = api.audit.get.useQuery(
     { id: currentAuditId! },
-    { enabled: !!currentAuditId, refetchInterval: 2000 }
+    { 
+        enabled: !!currentAuditId,
+        refetchInterval: audit?.status === AuditStatus.RUNNING ? 2000 : false
+    }
 );
apps/web/client/src/server/api/routers/cynthia/build-session-processor.ts (1)

179-181: Consider using a proper job queue for background processing.

The setTimeout(..., 0) pattern is fragile for production workloads. As noted in the comments, a proper job queue (BullMQ, Inngest, or similar) would provide better reliability, retries, and observability.

packages/ai/src/audit/engine.ts (3)

78-83: Consider stronger typing for token_changes before/after fields.

Using z.record(z.unknown()) for before and after provides minimal validation. If the structure is known, define an explicit schema to catch malformed AI responses early.


166-193: Type assertions bypass Zod's runtime validation guarantees.

Lines 187-188 cast teaser_issues and full_issues directly to DesignIssue[]. Since Zod already validated against DesignIssueSchema, this is safe, but the assertion obscures that the validated shape might differ from the TypeScript interface if they drift apart.

Consider using Zod's inferred types for the model or adding a runtime check.

🔎 Alternative approach using Zod inference
+type ValidatedDesignIssue = z.infer<typeof DesignIssueSchema>;
+
 function transformReportToModel(apiResponse: z.infer<typeof CynthiaReportSchema>): Partial<CynthiaReport> {
     return {
         // ...
-        teaserIssues: apiResponse.teaser_issues as DesignIssue[],
-        fullIssues: apiResponse.full_issues as DesignIssue[],
+        teaserIssues: apiResponse.teaser_issues,
+        fullIssues: apiResponse.full_issues,
         // ...
     };
 }

Then ensure CynthiaReport.teaserIssues accepts the Zod-inferred type or align the interfaces.


198-213: streamAudit returns unstructured text unlike runAudit.

streamAudit uses streamText which returns raw text chunks, while runAudit uses generateObject for structured output. Consumers expecting a CynthiaReport from the stream will need to parse the text manually. Consider documenting this difference or using streamObject if structured streaming is needed.

apps/web/client/src/server/api/routers/cynthia/build-session.ts (3)

73-79: Awaiting startBuildSessionAudit contradicts the "non-blocking" comment.

The comment says "non-blocking" but the code awaits the function. Looking at build-session-processor.ts, startBuildSessionAudit internally uses setTimeout(..., 0) for the actual processing, so the await only waits for audit creation, not processing. Clarify the comment to avoid confusion.

🔎 Suggested comment clarification
-            // Start audit processing in background (non-blocking)
+            // Queue audit for background processing (creates audit record, processing runs async)
             await startBuildSessionAudit({

49-54: Consider using next-intl for user-facing error messages.

Per coding guidelines, avoid hardcoded user-facing text. The error messages like 'Failed to create build session', 'Preview link not found', etc., should use internationalization. However, since these are server-side tRPC errors (not directly rendered UI text), this is a lower priority.

Also applies to: 66-71, 99-104, 116-121, 125-129, 145-150, 216-221, 224-229


185-200: Add descending order for session listing.

The list procedure orders by createdAt but doesn't specify direction. Users typically expect newest first.

🔎 Proposed fix
+import { desc } from 'drizzle-orm';
+
 // In list procedure:
             .from(buildSessions)
             .where(eq(buildSessions.userId, ctx.user.id))
-            .orderBy(buildSessions.createdAt);
+            .orderBy(desc(buildSessions.createdAt));
apps/web/client/src/server/api/routers/cynthia/audit.ts (1)

4-4: Remove unused import auditInsertSchema.

The auditInsertSchema is imported but never used in this file.

🔎 Proposed fix
-import { audits, auditInsertSchema } from '@onlook/db/src/schema';
+import { audits } from '@onlook/db/src/schema';
apps/backend/supabase/migrations/2025_12_19_add_build_sessions_and_preview_links.sql (1)

70-71: Redundant index on slug column.

The UNIQUE constraint on line 60 already creates an implicit index. The explicit idx_preview_links_slug index is redundant and wastes storage.

🔎 Proposed fix - remove redundant index
--- Remove this line:
-CREATE INDEX idx_preview_links_slug ON preview_links(slug);
packages/models/src/cynthia/report.ts (2)

13-16: Consider discriminated union for better type safety.

The AuditTarget interface uses a union type for the type field, but the value field is a generic string that could represent different data structures (URL, base64 screenshot, frame ID, component path). Consider using a discriminated union to provide stronger typing:

🔎 Proposed discriminated union approach
-export interface AuditTarget {
-    type: 'url' | 'screenshot' | 'frame' | 'component';
-    value: string;
-}
+export type AuditTarget =
+    | { type: 'url'; value: string }
+    | { type: 'screenshot'; value: string }
+    | { type: 'frame'; value: string }
+    | { type: 'component'; value: string };

Or if different types have different value shapes:

export type AuditTarget =
    | { type: 'url'; url: string }
    | { type: 'screenshot'; data: string }
    | { type: 'frame'; frameId: string }
    | { type: 'component'; path: string };

57-62: Consider more specific typing for token changes.

The TokenChange interface uses Record<string, unknown> for before and after fields, which provides flexibility but reduces type safety. Depending on your use case, you might benefit from more specific types:

🔎 Possible approaches for stronger typing

If token structures are known:

export interface ColorToken {
    name: string;
    value: string;
    opacity?: number;
}

export interface SpacingToken {
    name: string;
    value: number | string;
    unit?: string;
}

export type TokenChange = 
    | { type: 'color'; before: ColorToken; after: ColorToken; affectedComponents: string[] }
    | { type: 'spacing'; before: SpacingToken; after: SpacingToken; affectedComponents: string[] }
    | { type: 'typography'; before: Record<string, unknown>; after: Record<string, unknown>; affectedComponents: string[] }
    | { type: 'motion'; before: Record<string, unknown>; after: Record<string, unknown>; affectedComponents: string[] };

However, if token structures vary significantly, the current approach with Record<string, unknown> may be more pragmatic.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6962f87 and 17476be.

📒 Files selected for processing (46)
  • README.md (1 hunks)
  • apps/backend/supabase/migrations/2025_12_19_add_audit_fields_to_build_sessions.sql (1 hunks)
  • apps/backend/supabase/migrations/2025_12_19_add_build_sessions_and_preview_links.sql (1 hunks)
  • apps/web/client/messages/en.json (2 hunks)
  • apps/web/client/messages/es.json (1 hunks)
  • apps/web/client/src/app/_components/hero/index.tsx (2 hunks)
  • apps/web/client/src/app/_components/landing-page/synthia-sections.tsx (1 hunks)
  • apps/web/client/src/app/_components/landing-page/teaser-demo-section.tsx (1 hunks)
  • apps/web/client/src/app/layout.tsx (2 hunks)
  • apps/web/client/src/app/page.tsx (2 hunks)
  • apps/web/client/src/app/preview/[slug]/page.tsx (1 hunks)
  • apps/web/client/src/app/project/[id]/_components/right-panel/audit-tab/index.tsx (1 hunks)
  • apps/web/client/src/server/api/root.ts (2 hunks)
  • apps/web/client/src/server/api/routers/cynthia/audit.ts (1 hunks)
  • apps/web/client/src/server/api/routers/cynthia/build-session-processor.ts (1 hunks)
  • apps/web/client/src/server/api/routers/cynthia/build-session.ts (1 hunks)
  • apps/web/client/src/server/api/routers/cynthia/index.ts (1 hunks)
  • apps/web/client/src/server/api/routers/cynthia/preview.ts (1 hunks)
  • apps/web/client/src/server/api/routers/cynthia/processor.ts (1 hunks)
  • apps/web/client/src/server/api/routers/index.ts (1 hunks)
  • docs/CYNTHIA.md (1 hunks)
  • docs/CYNTHIA_ARCHITECTURE.md (1 hunks)
  • docs/CYNTHIA_IMPLEMENTATION_SUMMARY.md (1 hunks)
  • docs/build-my-site-flow.md (1 hunks)
  • packages/ai/src/agents/root.ts (2 hunks)
  • packages/ai/src/audit/engine.ts (1 hunks)
  • packages/ai/src/audit/example.ts (1 hunks)
  • packages/ai/src/audit/index.ts (1 hunks)
  • packages/ai/src/index.ts (1 hunks)
  • packages/ai/src/prompt/constants/cynthia.ts (1 hunks)
  • packages/ai/src/prompt/constants/index.ts (1 hunks)
  • packages/ai/src/tools/classes/cynthia-audit.ts (1 hunks)
  • packages/ai/src/tools/classes/index.ts (1 hunks)
  • packages/ai/src/tools/toolset.ts (2 hunks)
  • packages/ai/test/audit/engine.test.ts (1 hunks)
  • packages/db/src/schema/cynthia/audit.ts (1 hunks)
  • packages/db/src/schema/cynthia/build-session.ts (1 hunks)
  • packages/db/src/schema/cynthia/index.ts (1 hunks)
  • packages/db/src/schema/cynthia/preview-link.ts (1 hunks)
  • packages/db/src/schema/index.ts (1 hunks)
  • packages/models/src/chat/type.ts (1 hunks)
  • packages/models/src/cynthia/index.ts (1 hunks)
  • packages/models/src/cynthia/report.ts (1 hunks)
  • packages/models/src/cynthia/router.ts (1 hunks)
  • packages/models/src/cynthia/udec.ts (1 hunks)
  • packages/models/src/index.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (12)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Do not use the any type unless necessary

Files:

  • packages/models/src/cynthia/index.ts
  • packages/ai/src/prompt/constants/cynthia.ts
  • packages/ai/src/audit/index.ts
  • packages/ai/src/prompt/constants/index.ts
  • apps/web/client/src/server/api/routers/cynthia/index.ts
  • packages/ai/src/index.ts
  • apps/web/client/src/server/api/routers/index.ts
  • packages/ai/src/tools/classes/index.ts
  • packages/db/src/schema/index.ts
  • apps/web/client/src/app/preview/[slug]/page.tsx
  • packages/db/src/schema/cynthia/preview-link.ts
  • packages/models/src/index.ts
  • packages/ai/src/agents/root.ts
  • packages/models/src/chat/type.ts
  • packages/ai/src/audit/engine.ts
  • packages/models/src/cynthia/router.ts
  • packages/ai/src/audit/example.ts
  • apps/web/client/src/server/api/routers/cynthia/preview.ts
  • packages/db/src/schema/cynthia/index.ts
  • apps/web/client/src/server/api/root.ts
  • packages/models/src/cynthia/udec.ts
  • apps/web/client/src/server/api/routers/cynthia/processor.ts
  • apps/web/client/src/server/api/routers/cynthia/build-session-processor.ts
  • packages/db/src/schema/cynthia/audit.ts
  • packages/ai/src/tools/toolset.ts
  • apps/web/client/src/app/project/[id]/_components/right-panel/audit-tab/index.tsx
  • packages/ai/src/tools/classes/cynthia-audit.ts
  • apps/web/client/src/app/_components/landing-page/synthia-sections.tsx
  • apps/web/client/src/app/_components/hero/index.tsx
  • packages/ai/test/audit/engine.test.ts
  • apps/web/client/src/app/_components/landing-page/teaser-demo-section.tsx
  • apps/web/client/src/app/layout.tsx
  • apps/web/client/src/server/api/routers/cynthia/build-session.ts
  • packages/db/src/schema/cynthia/build-session.ts
  • packages/models/src/cynthia/report.ts
  • apps/web/client/src/server/api/routers/cynthia/audit.ts
  • apps/web/client/src/app/page.tsx
{apps,packages}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Avoid using the any type unless absolutely necessary

Files:

  • packages/models/src/cynthia/index.ts
  • packages/ai/src/prompt/constants/cynthia.ts
  • packages/ai/src/audit/index.ts
  • packages/ai/src/prompt/constants/index.ts
  • apps/web/client/src/server/api/routers/cynthia/index.ts
  • packages/ai/src/index.ts
  • apps/web/client/src/server/api/routers/index.ts
  • packages/ai/src/tools/classes/index.ts
  • packages/db/src/schema/index.ts
  • apps/web/client/src/app/preview/[slug]/page.tsx
  • packages/db/src/schema/cynthia/preview-link.ts
  • packages/models/src/index.ts
  • packages/ai/src/agents/root.ts
  • packages/models/src/chat/type.ts
  • packages/ai/src/audit/engine.ts
  • packages/models/src/cynthia/router.ts
  • packages/ai/src/audit/example.ts
  • apps/web/client/src/server/api/routers/cynthia/preview.ts
  • packages/db/src/schema/cynthia/index.ts
  • apps/web/client/src/server/api/root.ts
  • packages/models/src/cynthia/udec.ts
  • apps/web/client/src/server/api/routers/cynthia/processor.ts
  • apps/web/client/src/server/api/routers/cynthia/build-session-processor.ts
  • packages/db/src/schema/cynthia/audit.ts
  • packages/ai/src/tools/toolset.ts
  • apps/web/client/src/app/project/[id]/_components/right-panel/audit-tab/index.tsx
  • packages/ai/src/tools/classes/cynthia-audit.ts
  • apps/web/client/src/app/_components/landing-page/synthia-sections.tsx
  • apps/web/client/src/app/_components/hero/index.tsx
  • packages/ai/test/audit/engine.test.ts
  • apps/web/client/src/app/_components/landing-page/teaser-demo-section.tsx
  • apps/web/client/src/app/layout.tsx
  • apps/web/client/src/server/api/routers/cynthia/build-session.ts
  • packages/db/src/schema/cynthia/build-session.ts
  • packages/models/src/cynthia/report.ts
  • apps/web/client/src/server/api/routers/cynthia/audit.ts
  • apps/web/client/src/app/page.tsx
apps/web/client/src/server/api/routers/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/server/api/routers/**/*.ts: Place tRPC routers under apps/web/client/src/server/api/routers/**
Use publicProcedure/protectedProcedure from apps/web/client/src/server/api/trpc.ts and validate inputs with Zod
Return plain objects/arrays; rely on SuperJSON for serialization in tRPC procedures

apps/web/client/src/server/api/routers/**/*.ts: Place tRPC routers under src/server/api/routers/**
Use publicProcedure/protectedProcedure from src/server/api/trpc.ts and validate inputs with Zod
Return plain objects/arrays; rely on SuperJSON for serialization

Files:

  • apps/web/client/src/server/api/routers/cynthia/index.ts
  • apps/web/client/src/server/api/routers/index.ts
  • apps/web/client/src/server/api/routers/cynthia/preview.ts
  • apps/web/client/src/server/api/routers/cynthia/processor.ts
  • apps/web/client/src/server/api/routers/cynthia/build-session-processor.ts
  • apps/web/client/src/server/api/routers/cynthia/build-session.ts
  • apps/web/client/src/server/api/routers/cynthia/audit.ts
apps/web/client/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/**/*.{ts,tsx}: Use path aliases @/* and ~/* for imports that map to apps/web/client/src/*
Avoid hardcoded user-facing text; use next-intl messages/hooks instead

Use path aliases @/* and ~/* for imports mapping to src/*

Files:

  • apps/web/client/src/server/api/routers/cynthia/index.ts
  • apps/web/client/src/server/api/routers/index.ts
  • apps/web/client/src/app/preview/[slug]/page.tsx
  • apps/web/client/src/server/api/routers/cynthia/preview.ts
  • apps/web/client/src/server/api/root.ts
  • apps/web/client/src/server/api/routers/cynthia/processor.ts
  • apps/web/client/src/server/api/routers/cynthia/build-session-processor.ts
  • apps/web/client/src/app/project/[id]/_components/right-panel/audit-tab/index.tsx
  • apps/web/client/src/app/_components/landing-page/synthia-sections.tsx
  • apps/web/client/src/app/_components/hero/index.tsx
  • apps/web/client/src/app/_components/landing-page/teaser-demo-section.tsx
  • apps/web/client/src/app/layout.tsx
  • apps/web/client/src/server/api/routers/cynthia/build-session.ts
  • apps/web/client/src/server/api/routers/cynthia/audit.ts
  • apps/web/client/src/app/page.tsx
apps/web/client/src/app/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/app/**/*.tsx: Default to Server Components; add 'use client' when using events, state/effects, browser APIs, or client‑only libraries
Do not use process.env in client code; import env from @/env instead

Avoid hardcoded user-facing text; use next-intl messages/hooks

Files:

  • apps/web/client/src/app/preview/[slug]/page.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/audit-tab/index.tsx
  • apps/web/client/src/app/_components/landing-page/synthia-sections.tsx
  • apps/web/client/src/app/_components/hero/index.tsx
  • apps/web/client/src/app/_components/landing-page/teaser-demo-section.tsx
  • apps/web/client/src/app/layout.tsx
  • apps/web/client/src/app/page.tsx
apps/web/client/src/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/**/*.tsx: Create MobX store instances with useState(() => new Store()) for stable references across renders
Keep the active MobX store in a useRef and perform async cleanup with setTimeout(() => storeRef.current?.clear(), 0) to avoid route-change races
Avoid useMemo for creating MobX store instances
Avoid putting the MobX store instance in effect dependency arrays if it causes loops; split concerns by domain

apps/web/client/src/**/*.tsx: Create MobX store instances with useState(() => new Store()) for stable identities across renders
Keep the active MobX store in a useRef and clean up asynchronously with setTimeout(() => storeRef.current?.clear(), 0)
Do not use useMemo to create MobX stores
Avoid placing MobX store instances in effect dependency arrays if it causes loops; split concerns instead
observer components must be client components; place a single client boundary at the feature entry; child observers need not repeat 'use client'

Files:

  • apps/web/client/src/app/preview/[slug]/page.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/audit-tab/index.tsx
  • apps/web/client/src/app/_components/landing-page/synthia-sections.tsx
  • apps/web/client/src/app/_components/hero/index.tsx
  • apps/web/client/src/app/_components/landing-page/teaser-demo-section.tsx
  • apps/web/client/src/app/layout.tsx
  • apps/web/client/src/app/page.tsx
apps/web/client/src/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Default to Server Components; add 'use client' only when using events, state/effects, browser APIs, or client-only libs

Files:

  • apps/web/client/src/app/preview/[slug]/page.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/audit-tab/index.tsx
  • apps/web/client/src/app/_components/landing-page/synthia-sections.tsx
  • apps/web/client/src/app/_components/hero/index.tsx
  • apps/web/client/src/app/_components/landing-page/teaser-demo-section.tsx
  • apps/web/client/src/app/layout.tsx
  • apps/web/client/src/app/page.tsx
apps/web/client/messages/**

📄 CodeRabbit inference engine (AGENTS.md)

Add or modify internationalized strings in apps/web/client/messages/* instead of hardcoding text

Files:

  • apps/web/client/messages/en.json
  • apps/web/client/messages/es.json
apps/web/client/messages/**/*.{json,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

Add or modify translation keys in apps/web/client/messages/* and keep keys stable (prefer additions over breaking renames)

Files:

  • apps/web/client/messages/en.json
  • apps/web/client/messages/es.json
apps/web/client/src/server/api/root.ts

📄 CodeRabbit inference engine (AGENTS.md)

Export all tRPC routers from apps/web/client/src/server/api/root.ts

Export all tRPC routers from src/server/api/root.ts

Files:

  • apps/web/client/src/server/api/root.ts
apps/web/client/src/app/layout.tsx

📄 CodeRabbit inference engine (AGENTS.md)

Preserve dark theme defaults via ThemeProvider usage in the root layout

Preserve dark theme defaults via ThemeProvider in the root layout

Files:

  • apps/web/client/src/app/layout.tsx
apps/web/client/src/app/{page,layout,route}.tsx

📄 CodeRabbit inference engine (CLAUDE.md)

Follow App Router file conventions (page.tsx, layout.tsx, route.ts) within src/app

Files:

  • apps/web/client/src/app/layout.tsx
  • apps/web/client/src/app/page.tsx
🧠 Learnings (20)
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/server/api/root.ts : Export all tRPC routers from src/server/api/root.ts

Applied to files:

  • packages/models/src/cynthia/index.ts
  • packages/ai/src/prompt/constants/index.ts
  • apps/web/client/src/server/api/routers/cynthia/index.ts
  • packages/ai/src/index.ts
  • apps/web/client/src/server/api/routers/index.ts
  • packages/models/src/index.ts
  • packages/models/src/cynthia/router.ts
  • apps/web/client/src/server/api/routers/cynthia/preview.ts
  • apps/web/client/src/server/api/root.ts
  • apps/web/client/src/server/api/routers/cynthia/build-session.ts
  • apps/web/client/src/server/api/routers/cynthia/audit.ts
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/server/api/root.ts : Export all tRPC routers from apps/web/client/src/server/api/root.ts

Applied to files:

  • packages/models/src/cynthia/index.ts
  • packages/ai/src/prompt/constants/index.ts
  • apps/web/client/src/server/api/routers/cynthia/index.ts
  • packages/ai/src/index.ts
  • apps/web/client/src/server/api/routers/index.ts
  • packages/models/src/index.ts
  • packages/models/src/cynthia/router.ts
  • apps/web/client/src/server/api/routers/cynthia/preview.ts
  • apps/web/client/src/server/api/root.ts
  • apps/web/client/src/server/api/routers/cynthia/build-session.ts
  • apps/web/client/src/server/api/routers/cynthia/audit.ts
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/server/api/routers/**/*.ts : Place tRPC routers under apps/web/client/src/server/api/routers/**

Applied to files:

  • apps/web/client/src/server/api/routers/cynthia/index.ts
  • apps/web/client/src/server/api/routers/index.ts
  • packages/models/src/cynthia/router.ts
  • apps/web/client/src/server/api/routers/cynthia/preview.ts
  • apps/web/client/src/server/api/root.ts
  • apps/web/client/src/server/api/routers/cynthia/build-session.ts
  • apps/web/client/src/server/api/routers/cynthia/audit.ts
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/server/api/routers/**/*.ts : Place tRPC routers under src/server/api/routers/**

Applied to files:

  • apps/web/client/src/server/api/routers/cynthia/index.ts
  • apps/web/client/src/server/api/routers/index.ts
  • packages/models/src/cynthia/router.ts
  • apps/web/client/src/server/api/routers/cynthia/preview.ts
  • apps/web/client/src/server/api/root.ts
  • apps/web/client/src/server/api/routers/cynthia/build-session.ts
  • apps/web/client/src/server/api/routers/cynthia/audit.ts
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/server/api/routers/**/*.ts : Use publicProcedure/protectedProcedure from src/server/api/trpc.ts and validate inputs with Zod

Applied to files:

  • apps/web/client/src/server/api/routers/cynthia/index.ts
  • apps/web/client/src/server/api/routers/index.ts
  • apps/web/client/src/server/api/routers/cynthia/preview.ts
  • apps/web/client/src/server/api/root.ts
  • apps/web/client/src/server/api/routers/cynthia/build-session.ts
  • apps/web/client/src/server/api/routers/cynthia/audit.ts
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/server/api/routers/**/*.ts : Use publicProcedure/protectedProcedure from apps/web/client/src/server/api/trpc.ts and validate inputs with Zod

Applied to files:

  • apps/web/client/src/server/api/routers/cynthia/index.ts
  • apps/web/client/src/server/api/routers/index.ts
  • apps/web/client/src/server/api/routers/cynthia/preview.ts
  • apps/web/client/src/server/api/root.ts
  • apps/web/client/src/server/api/routers/cynthia/build-session.ts
  • apps/web/client/src/server/api/routers/cynthia/audit.ts
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Prefer importing env from @/env; avoid process.env in client code and guard shared modules with typeof window === 'undefined'

Applied to files:

  • apps/web/client/src/server/api/routers/cynthia/index.ts
  • packages/db/src/schema/cynthia/index.ts
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/**/*.{ts,tsx} : Use path aliases @/* and ~/* for imports that map to apps/web/client/src/*

Applied to files:

  • apps/web/client/src/server/api/routers/index.ts
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/server/api/routers/**/*.ts : Return plain objects/arrays; rely on SuperJSON for serialization

Applied to files:

  • apps/web/client/src/server/api/routers/index.ts
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/app/{page,layout,route}.tsx : Follow App Router file conventions (page.tsx, layout.tsx, route.ts) within src/app

Applied to files:

  • apps/web/client/src/app/preview/[slug]/page.tsx
  • apps/web/client/src/app/_components/hero/index.tsx
  • apps/web/client/src/app/page.tsx
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/server/api/routers/**/*.ts : Return plain objects/arrays; rely on SuperJSON for serialization in tRPC procedures

Applied to files:

  • apps/web/client/src/server/api/routers/cynthia/preview.ts
  • apps/web/client/src/server/api/root.ts
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/messages/** : Add or modify internationalized strings in apps/web/client/messages/* instead of hardcoding text

Applied to files:

  • apps/web/client/messages/en.json
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/messages/**/*.{json,ts} : Add or modify translation keys in apps/web/client/messages/* and keep keys stable (prefer additions over breaking renames)

Applied to files:

  • apps/web/client/messages/en.json
  • apps/web/client/messages/es.json
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/app/**/*.tsx : Default to Server Components; add 'use client' when using events, state/effects, browser APIs, or client‑only libraries

Applied to files:

  • apps/web/client/src/app/_components/landing-page/synthia-sections.tsx
  • apps/web/client/src/app/_components/hero/index.tsx
  • apps/web/client/src/app/_components/landing-page/teaser-demo-section.tsx
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/app/**/*.{ts,tsx} : Default to Server Components; add 'use client' only when using events, state/effects, browser APIs, or client-only libs

Applied to files:

  • apps/web/client/src/app/_components/landing-page/synthia-sections.tsx
  • apps/web/client/src/app/_components/landing-page/teaser-demo-section.tsx
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/**/*.{ts,tsx} : Avoid hardcoded user-facing text; use next-intl messages/hooks instead

Applied to files:

  • apps/web/client/src/app/_components/landing-page/synthia-sections.tsx
  • apps/web/client/src/app/_components/hero/index.tsx
  • apps/web/client/src/app/_components/landing-page/teaser-demo-section.tsx
  • apps/web/client/src/app/layout.tsx
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/app/**/*.tsx : Avoid hardcoded user-facing text; use next-intl messages/hooks

Applied to files:

  • apps/web/client/src/app/_components/landing-page/synthia-sections.tsx
  • apps/web/client/src/app/_components/hero/index.tsx
  • apps/web/client/src/app/_components/landing-page/teaser-demo-section.tsx
  • apps/web/client/src/app/layout.tsx
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/**/*.tsx : observer components must be client components; place a single client boundary at the feature entry; child observers need not repeat 'use client'

Applied to files:

  • apps/web/client/src/app/_components/landing-page/synthia-sections.tsx
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/app/layout.tsx : Preserve dark theme defaults via ThemeProvider usage in the root layout

Applied to files:

  • apps/web/client/src/app/layout.tsx
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/app/layout.tsx : Preserve dark theme defaults via ThemeProvider in the root layout

Applied to files:

  • apps/web/client/src/app/layout.tsx
🧬 Code graph analysis (16)
packages/db/src/schema/cynthia/preview-link.ts (1)
packages/db/src/schema/cynthia/build-session.ts (1)
  • buildSessions (28-57)
packages/ai/src/agents/root.ts (1)
packages/ai/src/prompt/constants/cynthia.ts (1)
  • getCynthiaSystemPrompt (145-147)
packages/ai/src/audit/engine.ts (5)
packages/models/src/cynthia/report.ts (2)
  • AuditInput (7-11)
  • CynthiaReport (30-45)
packages/models/src/cynthia/router.ts (1)
  • TASK_ROUTING (57-66)
packages/ai/src/chat/providers.ts (1)
  • initModel (12-46)
packages/ai/src/prompt/constants/cynthia.ts (1)
  • getCynthiaSystemPrompt (145-147)
packages/models/src/cynthia/udec.ts (1)
  • DesignIssue (53-63)
packages/ai/src/audit/example.ts (2)
packages/models/src/cynthia/report.ts (2)
  • AuditInput (7-11)
  • CynthiaReport (30-45)
packages/ai/src/audit/engine.ts (1)
  • runAudit (99-124)
apps/web/client/src/server/api/routers/cynthia/preview.ts (3)
apps/web/client/src/server/api/trpc.ts (1)
  • createTRPCRouter (89-89)
packages/db/src/schema/cynthia/preview-link.ts (1)
  • previewLinks (8-32)
packages/db/src/schema/cynthia/build-session.ts (1)
  • buildSessions (28-57)
apps/web/client/src/server/api/root.ts (4)
apps/web/client/src/server/api/routers/cynthia/audit.ts (1)
  • auditRouter (9-267)
apps/web/client/src/server/api/routers/cynthia/build-session.ts (1)
  • buildSessionRouter (22-242)
apps/web/client/src/server/api/routers/cynthia/preview.ts (1)
  • previewRouter (7-66)
apps/web/client/src/server/api/routers/domain/preview.ts (1)
  • previewRouter (9-60)
apps/web/client/src/server/api/routers/cynthia/processor.ts (4)
packages/db/src/client.ts (1)
  • db (16-16)
packages/db/src/schema/cynthia/audit.ts (1)
  • audits (16-41)
packages/models/src/cynthia/report.ts (1)
  • AuditInput (7-11)
packages/ai/src/audit/engine.ts (1)
  • runAudit (99-124)
packages/ai/src/tools/toolset.ts (2)
packages/ai/src/tools/classes/cynthia-audit.ts (1)
  • CynthiaAuditTool (6-99)
packages/ai/src/tools/classes/index.ts (1)
  • CynthiaAuditTool (4-4)
apps/web/client/src/app/project/[id]/_components/right-panel/audit-tab/index.tsx (1)
packages/ui/src/components/icons/index.tsx (1)
  • Icons (138-3667)
packages/ai/src/tools/classes/cynthia-audit.ts (2)
packages/ui/src/components/icons/index.tsx (1)
  • Icons (138-3667)
apps/web/client/src/components/store/editor/engine.ts (1)
  • EditorEngine (33-135)
apps/web/client/src/app/_components/landing-page/synthia-sections.tsx (2)
apps/web/client/src/app/project/[id]/_components/top-bar/publish/dropdown/custom-domain/index.tsx (1)
  • Section (14-24)
packages/ui/src/components/icons/index.tsx (1)
  • Icons (138-3667)
apps/web/client/src/app/_components/hero/index.tsx (3)
apps/web/client/src/app/_components/hero/unicorn-background.tsx (1)
  • UnicornBackground (6-31)
apps/web/client/src/app/_components/hero/high-demand.tsx (1)
  • HighDemand (3-22)
apps/web/client/src/app/_components/hero/create-error.tsx (1)
  • CreateError (5-20)
packages/ai/test/audit/engine.test.ts (1)
packages/models/src/cynthia/report.ts (1)
  • AuditInput (7-11)
apps/web/client/src/server/api/routers/cynthia/build-session.ts (4)
apps/web/client/src/server/api/trpc.ts (2)
  • createTRPCRouter (89-89)
  • protectedProcedure (131-150)
packages/db/src/schema/cynthia/build-session.ts (1)
  • buildSessions (28-57)
packages/db/src/schema/cynthia/preview-link.ts (1)
  • previewLinks (8-32)
apps/web/client/src/server/api/routers/cynthia/build-session-processor.ts (1)
  • startBuildSessionAudit (134-184)
packages/models/src/cynthia/report.ts (1)
packages/models/src/cynthia/udec.ts (2)
  • UDECScores (37-51)
  • DesignIssue (53-63)
apps/web/client/src/server/api/routers/cynthia/audit.ts (2)
apps/web/client/src/server/api/trpc.ts (1)
  • protectedProcedure (131-150)
apps/web/client/src/server/api/routers/cynthia/processor.ts (1)
  • startAuditProcessing (95-103)
🪛 LanguageTool
docs/CYNTHIA_IMPLEMENTATION_SUMMARY.md

[uncategorized] ~437-~437: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...No Batch Processing* - One audit at a time 5. Limited Context - No historical project data ...

(EN_COMPOUND_ADJECTIVE_INTERNAL)

🪛 markdownlint-cli2 (0.18.1)
docs/CYNTHIA.md

215-215: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

docs/build-my-site-flow.md

295-295: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

docs/CYNTHIA_ARCHITECTURE.md

215-215: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

Comment on lines +136 to +142
-- RLS Policy: No public insert (only server/authenticated can create)
-- Rationale: Prevent spam, only app can generate preview links
DROP POLICY IF EXISTS "preview_links_insert_policy" ON preview_links;
CREATE POLICY "preview_links_insert_policy" ON preview_links
FOR INSERT
TO authenticated
WITH CHECK (true);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

preview_links_insert_policy blocks anonymous session creation flow.

The policy only allows authenticated users to insert preview links, but the application flow (per build-session.ts) creates preview links for anonymous users. This will cause inserts to fail for anonymous sessions unless the insert bypasses RLS (e.g., using a service role).

🔎 Potential fixes

Option 1: Allow anon to insert (if acceptable):

 CREATE POLICY "preview_links_insert_policy" ON preview_links
 FOR INSERT
-TO authenticated
+TO anon, authenticated
 WITH CHECK (true);

Option 2: Ensure the tRPC router uses a service role connection that bypasses RLS for anonymous session creation. Verify this is the intended design.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
-- RLS Policy: No public insert (only server/authenticated can create)
-- Rationale: Prevent spam, only app can generate preview links
DROP POLICY IF EXISTS "preview_links_insert_policy" ON preview_links;
CREATE POLICY "preview_links_insert_policy" ON preview_links
FOR INSERT
TO authenticated
WITH CHECK (true);
-- RLS Policy: No public insert (only server/authenticated can create)
-- Rationale: Prevent spam, only app can generate preview links
DROP POLICY IF EXISTS "preview_links_insert_policy" ON preview_links;
CREATE POLICY "preview_links_insert_policy" ON preview_links
FOR INSERT
TO anon, authenticated
WITH CHECK (true);

Comment on lines +36 to +72
const createBuildSession = api.buildSession.create.useMutation({
onSuccess: (data) => {
// Navigate to preview page
router.push(`/preview/${data.previewSlug}`);
},
onError: (error) => {
toast.error('Failed to create build session', {
description: error.message,
});
},
});

/**
* Detect if input is a URL or an idea
*/
const detectInputType = (input: string): 'url' | 'idea' => {
try {
new URL(input);
return 'url';
} catch {
return 'idea';
}
};

const handleGetScore = () => {
if (!urlInput.trim()) {
toast.error('Please enter a URL or describe your idea');
return;
}

const inputType = detectInputType(urlInput);
createBuildSession.mutate({
inputType,
inputValue: urlInput,
language: 'en', // TODO: Get from locale
});
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Check the actual file content
cat -n apps/web/client/src/app/_components/hero/index.tsx | head -100

Repository: onlook-dev/onlook

Length of output: 4526


🏁 Script executed:

# Check if there are any other imports or if useLocale is used elsewhere in the file
wc -l apps/web/client/src/app/_components/hero/index.tsx

Repository: onlook-dev/onlook

Length of output: 115


🏁 Script executed:

# Get the total line count
wc -l apps/web/client/src/app/_components/hero/index.tsx

Repository: onlook-dev/onlook

Length of output: 115


🏁 Script executed:

# Search for buildSession.create definition and schema
fd -t f "router\|schema\|types" apps/web/server --type f | head -20

Repository: onlook-dev/onlook

Length of output: 43


🏁 Script executed:

# Search for buildSession in the API routes
rg -l "buildSession" apps/web/server

Repository: onlook-dev/onlook

Length of output: 43


🏁 Script executed:

# Search for buildSession creation in server code
rg "buildSession.*create" apps/web/server --type ts --type tsx -A 5

Repository: onlook-dev/onlook

Length of output: 88


🏁 Script executed:

# Search for language field definition in schema or types
rg "language.*:" apps/web/server --type ts --type tsx | head -20

Repository: onlook-dev/onlook

Length of output: 88


🏁 Script executed:

# Check for useLocale usage in client code
rg "useLocale" apps/web/client/src --type ts --type tsx -B 2 -A 2

Repository: onlook-dev/onlook

Length of output: 88


🏁 Script executed:

# Search for buildSession in server directory
find apps/web/server -type f \( -name "*.ts" -o -name "*.tsx" \) | xargs grep -l "buildSession" 2>/dev/null | head -5

Repository: onlook-dev/onlook

Length of output: 43


🏁 Script executed:

# Search for useLocale in client code
find apps/web/client/src -type f \( -name "*.ts" -o -name "*.tsx" \) | xargs grep -l "useLocale" 2>/dev/null

Repository: onlook-dev/onlook

Length of output: 45


🏁 Script executed:

# Search for the buildSession router definition
find apps/web/server -type f \( -name "*.ts" -o -name "*.tsx" \) | xargs grep -l "buildSession.*router" 2>/dev/null

Repository: onlook-dev/onlook

Length of output: 45


🏁 Script executed:

# Get a broader view of the server structure
find apps/web/server -type f -name "*.ts" | head -20

Repository: onlook-dev/onlook

Length of output: 310


🏁 Script executed:

# Search for buildSession in the router
grep -r "buildSession" apps/web/server/src/router/ 2>/dev/null

Repository: onlook-dev/onlook

Length of output: 43


🏁 Script executed:

# Check the main router index
cat apps/web/server/src/router/index.ts

Repository: onlook-dev/onlook

Length of output: 253


🏁 Script executed:

# Check sandbox route for buildSession
cat apps/web/server/src/router/routes/sandbox.ts

Repository: onlook-dev/onlook

Length of output: 1011


🏁 Script executed:

# Search for language or inputType references in server
grep -r "language\|inputType" apps/web/server/src --include="*.ts" 2>/dev/null

Repository: onlook-dev/onlook

Length of output: 43


🏁 Script executed:

# Search for buildSession in all server files
grep -r "buildSession" apps/web/server/src --include="*.ts" 2>/dev/null

Repository: onlook-dev/onlook

Length of output: 43


🏁 Script executed:

# Search for "create" mutation with object input patterns
grep -r "\.input(" apps/web/server/src/router/routes --include="*.ts" -A 2 | head -40

Repository: onlook-dev/onlook

Length of output: 964


🏁 Script executed:

# List all files in router/routes
ls -la apps/web/server/src/router/routes/

Repository: onlook-dev/onlook

Length of output: 228


🏁 Script executed:

# Search for buildSession in the entire apps/web directory
grep -r "buildSession" apps/web/ --include="*.ts" --include="*.tsx" 2>/dev/null

Repository: onlook-dev/onlook

Length of output: 11447


🏁 Script executed:

# Check the TRPC client setup to understand the API structure
find apps/web/client/src -name "*trpc*" -o -name "*api*" | head -10

Repository: onlook-dev/onlook

Length of output: 304


🏁 Script executed:

# Look for the API client configuration
find apps/web/client/src -type f \( -name "*.ts" -o -name "*.tsx" \) | xargs grep -l "api\." 2>/dev/null | head -5

Repository: onlook-dev/onlook

Length of output: 344


🏁 Script executed:

# Look at the buildSession router to see the create mutation schema
cat apps/web/client/src/server/api/routers/cynthia/build-session.ts | head -100

Repository: onlook-dev/onlook

Length of output: 3506


🏁 Script executed:

# Find next-intl configuration files
find apps/web/client/src -name "*i18n*" -o -name "*intl*" -o -name "*locale*" | head -10

Repository: onlook-dev/onlook

Length of output: 85


🏁 Script executed:

# Check for i18n routing or config files
find apps/web/client -type f \( -name "*.ts" -o -name "*.tsx" \) | xargs grep -l "routing\|locales" 2>/dev/null | grep -v node_modules | head -10

Repository: onlook-dev/onlook

Length of output: 98


🏁 Script executed:

# Look for i18n config in the root or specific directories
find apps/web -path "*/node_modules" -prune -o -type f -name "*routing*" -print

Repository: onlook-dev/onlook

Length of output: 43


🏁 Script executed:

# Check the i18n directory structure
ls -la apps/web/client/src/i18n/

Repository: onlook-dev/onlook

Length of output: 292


🏁 Script executed:

# Check the contents of the i18n directory
find apps/web/client/src/i18n -type f

Repository: onlook-dev/onlook

Length of output: 129


🏁 Script executed:

# Check the i18n configuration files
cat apps/web/client/src/i18n/keys.ts

Repository: onlook-dev/onlook

Length of output: 942


🏁 Script executed:

cat apps/web/client/src/i18n/request.ts

Repository: onlook-dev/onlook

Length of output: 1213


🏁 Script executed:

# Find the Language constant definition
find apps -name "constants*" -type f | xargs grep -l "Language" 2>/dev/null | head -5

Repository: onlook-dev/onlook

Length of output: 43


🏁 Script executed:

# Search for Language enum definition
grep -r "enum Language\|export const Language" apps --include="*.ts" --include="*.tsx" 2>/dev/null

Repository: onlook-dev/onlook

Length of output: 43


🏁 Script executed:

# Check the @onlook/constants package
find packages -name "*constants*" -type d

Repository: onlook-dev/onlook

Length of output: 112


🏁 Script executed:

# Look for Language export in packages
grep -r "Language" packages --include="*.ts" --include="*.tsx" 2>/dev/null | grep -E "export|enum|const" | head -10

Repository: onlook-dev/onlook

Length of output: 589


🏁 Script executed:

# Check the Language enum definition
cat packages/constants/src/language.ts

Repository: onlook-dev/onlook

Length of output: 371


Use useLocale() for dynamic locale, but note backend language limitation.

The hardcoded language: 'en' should be replaced with useLocale() to respect the user's language preference. However, the backend schema only accepts ['en', 'es'], while the app supports 4 languages (en, ja, zh, ko). Before implementing this change, either:

  1. Expand the backend schema to accept all supported languages, or
  2. Map useLocale() to a fallback value when the locale isn't 'en' or 'es', or
  3. Restrict the next-intl configuration to only 'en' and 'es'
 import { useEffect, useState } from 'react';
 import { motion } from 'motion/react';
-import { useTranslations } from 'next-intl';
+import { useTranslations, useLocale } from 'next-intl';
 import { useRouter } from 'next/navigation';

 export function Hero() {
     const t = useTranslations('landing.hero');
+    const locale = useLocale();
     const router = useRouter();
     
     // ... later in handleGetScore:
     createBuildSession.mutate({
         inputType,
         inputValue: urlInput,
-        language: 'en', // TODO: Get from locale
+        language: locale as 'en' | 'es', // Ensure value matches backend enum
     });

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/web/client/src/app/_components/hero/index.tsx around lines 36 to 72,
replace the hardcoded language: 'en' with a locale-aware value from useLocale();
import and call useLocale() at the top of the component, then map its return to
the backend-allowed values ('en' or 'es') (e.g. locale startsWith 'es' -> 'es',
otherwise fallback to 'en') before passing to createBuildSession.mutate;
alternatively, if you prefer backend change, coordinate to expand the schema to
accept the app's supported locales ('en','ja','zh','ko') and then pass
useLocale() directly.

Comment on lines +68 to +99
if (previewLoading || teaserLoading) {
return (
<WebsiteLayout showFooter={false}>
<div className="min-h-screen flex items-center justify-center">
<div className="flex flex-col items-center gap-4">
<Icons.Spinner className="h-8 w-8 animate-spin text-foreground-secondary" />
<p className="text-foreground-secondary">Loading preview...</p>
</div>
</div>
</WebsiteLayout>
);
}

if (previewError || !previewData || !teaserData) {
return (
<WebsiteLayout showFooter={false}>
<div className="min-h-screen flex items-center justify-center">
<div className="flex flex-col items-center gap-4 text-center max-w-md">
<Icons.AlertTriangle className="h-12 w-12 text-destructive" />
<h1 className="text-2xl font-bold">Preview Not Found</h1>
<p className="text-foreground-secondary">
{previewError?.message || 'This preview link is invalid or has expired.'}
</p>
<Button onClick={() => router.push('/')}>
<Icons.ArrowLeft className="h-4 w-4 mr-2" />
Back to Home
</Button>
</div>
</div>
</WebsiteLayout>
);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Hardcoded user-facing text should use i18n.

As per coding guidelines, avoid hardcoded user-facing text and use next-intl messages instead. This file has numerous hardcoded strings (e.g., "Loading preview...", "Preview Not Found", "Back to Home"). The help.cynthia section in en.json provides some relevant keys, but additional keys may be needed.

Example locations to internationalize:

  • Line 74: "Loading preview..."
  • Line 87: "Preview Not Found"
  • Line 122: "Running Cynthia Audit"
  • Line 150: "Audit Failed"
  • Lines 201, 222, 231, 248, 304: Score/issue labels

Comment on lines +102 to +141
if (teaserData.auditStatus !== 'completed') {
const statusText = teaserData.auditStatus === 'pending'
? 'Starting audit...'
: teaserData.auditStatus === 'running'
? 'Analyzing your UI...'
: 'Processing...';

const progress = teaserData.progress || 0;

return (
<WebsiteLayout showFooter={false}>
<div className="min-h-screen py-12 px-4">
<div className="max-w-4xl mx-auto">
<motion.div
className="flex flex-col items-center justify-center min-h-[60vh] gap-6"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
>
<Icons.Spinner className="h-16 w-16 animate-spin text-foreground-secondary" />
<div className="text-center">
<h1 className="text-3xl font-bold mb-2">Running Cynthia Audit</h1>
<p className="text-foreground-secondary mb-4">{statusText}</p>
<div className="w-64 h-2 bg-background-secondary rounded-full overflow-hidden">
<motion.div
className="h-full bg-foreground-primary"
initial={{ width: '0%' }}
animate={{ width: `${progress}%` }}
transition={{ duration: 0.5 }}
/>
</div>
</div>
<p className="text-sm text-foreground-secondary max-w-md text-center">
We're analyzing {teaserData.inputType === 'url' ? 'your URL' : 'your idea'} using the UDEC framework. This usually takes 30-60 seconds.
</p>
</motion.div>
</div>
</div>
</WebsiteLayout>
);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Bug: Failed status shows progress UI instead of failure UI.

When auditStatus === 'failed', the condition at line 102 (!== 'completed') is true, so the progress UI is rendered and the component returns early. The failed state check at line 144 becomes unreachable.

🔎 Proposed fix
-    // Show progress if audit is still running
-    if (teaserData.auditStatus !== 'completed') {
+    // Show progress if audit is still running (not completed and not failed)
+    if (teaserData.auditStatus !== 'completed' && teaserData.auditStatus !== 'failed') {

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +165 to +175
const teaserSummary = teaserData.teaserSummary as {
issuesFound: number;
teaserIssues: Array<{
severity: string;
axis: string;
title: string;
description: string;
reason: string;
impact: string;
}>;
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Unsafe type assertion on teaserSummary could cause runtime errors.

The teaserSummary is cast directly without validation. If the backend returns unexpected data or null, accessing properties like issuesFound and teaserIssues will throw.

🔎 Proposed fix with validation
-    const teaserSummary = teaserData.teaserSummary as {
-        issuesFound: number;
-        teaserIssues: Array<{
-            severity: string;
-            axis: string;
-            title: string;
-            description: string;
-            reason: string;
-            impact: string;
-        }>;
-    };
+    // Safely access teaserSummary with fallbacks
+    const teaserSummary = (teaserData.teaserSummary ?? {}) as {
+        issuesFound?: number;
+        teaserIssues?: Array<{
+            severity: string;
+            axis: string;
+            title: string;
+            description: string;
+            reason: string;
+            impact: string;
+        }>;
+    };
+    const issuesFound = teaserSummary.issuesFound ?? 0;
+    const teaserIssues = teaserSummary.teaserIssues ?? [];

Then update references to use issuesFound and teaserIssues instead of teaserSummary.issuesFound and teaserSummary.teaserIssues.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/web/client/src/app/preview/[slug]/page.tsx around lines 165 to 175, the
code unsafely asserts teaserData.teaserSummary to a specific shape which can
throw if the backend returns null or an unexpected shape; validate the value
first (null check and runtime shape check or a small type-guard) before using
it, derive safe local variables like issuesFound and teaserIssues with sensible
defaults (e.g., 0 and empty array) when validation fails, replace direct uses of
teaserSummary.issuesFound and teaserSummary.teaserIssues with these safe
variables, and avoid the direct cast so runtime exceptions are prevented.

Comment on lines +12 to +20
function generatePreviewSlug(): string {
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
const length = 12; // 12 chars = 62^12 combinations (unguessable)
let slug = '';
for (let i = 0; i < length; i++) {
slug += chars.charAt(Math.floor(Math.random() * chars.length));
}
return slug;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use cryptographically secure random generation for preview slugs.

Math.random() is not cryptographically secure and can be predictable. For unguessable slugs protecting audit data, use crypto.randomBytes or crypto.getRandomValues.

🔎 Proposed fix using Node.js crypto
+import { randomBytes } from 'crypto';
+
 function generatePreviewSlug(): string {
     const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
     const length = 12;
-    let slug = '';
-    for (let i = 0; i < length; i++) {
-        slug += chars.charAt(Math.floor(Math.random() * chars.length));
-    }
-    return slug;
+    const bytes = randomBytes(length);
+    return Array.from(bytes)
+        .map(byte => chars[byte % chars.length])
+        .join('');
 }
🤖 Prompt for AI Agents
In apps/web/client/src/server/api/routers/cynthia/build-session.ts around lines
12-20, the slug generator uses Math.random(), which is not cryptographically
secure; replace it with Node's crypto random generator: import crypto from
'crypto' and generate secure bytes (crypto.randomBytes) then map those bytes to
characters from the allowed charset, using rejection sampling or
sufficient-range mapping to avoid modulo bias, to produce a 12-character
unguessable slug and return it.

Comment on lines +44 to +45
context: audit.context as any,
constraints: audit.constraints as any,
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Avoid using any type assertions for context and constraints.

The type assertions to any bypass type safety and could hide mismatches between the JSONB database columns and the expected AuditContext/AuditConstraints types. Consider adding proper type guards or using Zod parsing to validate the JSONB data.

Based on coding guidelines: "Do not use the any type unless necessary."

🔎 Suggested improvement
// Import the Zod schemas for validation
import { AuditInputSchema } from '@onlook/models';

// In processAudit:
const auditInput: AuditInput = {
    target: {
        type: audit.targetType as 'url' | 'screenshot' | 'frame' | 'component',
        value: audit.targetValue,
    },
    context: audit.context ? AuditInputSchema.shape.context.parse(audit.context) : undefined,
    constraints: audit.constraints ? AuditInputSchema.shape.constraints.parse(audit.constraints) : undefined,
};
🤖 Prompt for AI Agents
In apps/web/client/src/server/api/routers/cynthia/processor.ts around lines
44-45, avoid the `as any` assertions for audit.context and audit.constraints;
instead validate and coerce the JSONB payload to the expected types using the
project's Zod schemas (or explicit type guards). Import the appropriate
AuditInput/AuditInputSchema, then replace the casts by parsing: if
audit.context/constraints exist, call AuditInputSchema.shape.context.parse(...)
/ .shape.constraints.parse(...) (or use safeParse and handle failure by
returning a validation error), and pass the parsed/typed values into the
constructed audit input; remove the `any` casts and ensure errors are handled
(throw or return a 400) when parsing fails.

Comment on lines +99 to +124
export async function runAudit(input: AuditInput): Promise<Partial<CynthiaReport>> {
const modelTier = TASK_ROUTING[TaskRoute.UI_AUDIT_REASONING];
const { model } = initModel({
provider: modelTier.provider,
model: modelTier.model as OPENROUTER_MODELS,
});

const systemPrompt = getCynthiaSystemPrompt();

// Build the user prompt with context
const userPrompt = buildAuditPrompt(input);

try {
const { object } = await generateObject({
model,
schema: CynthiaReportSchema,
system: systemPrompt,
prompt: userPrompt,
});

return transformReportToModel(object);
} catch (error) {
console.error('Audit failed:', error);
throw new Error(`Audit execution failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Locate the file and understand TASK_ROUTING and related types
fd -t f "engine.ts" packages/ai/src/audit/ 2>/dev/null && echo "=== Found engine.ts ===" && cat -n packages/ai/src/audit/engine.ts | head -130

Repository: onlook-dev/onlook

Length of output: 5314


🏁 Script executed:

#!/bin/bash
# Find the models package and look for TASK_ROUTING and OPENROUTER_MODELS definitions
fd -t f "*.ts" -path "*/models/*" | head -20 && echo "=== Searching for TASK_ROUTING ===" && rg -n "TASK_ROUTING\s*=" --type=ts -A 5

Repository: onlook-dev/onlook

Length of output: 281


🏁 Script executed:

#!/bin/bash
# Find models directory and TASK_ROUTING definition
find . -type d -name "models" 2>/dev/null | head -5
echo "=== Searching for TASK_ROUTING definition ==="
rg -n "TASK_ROUTING\s*=" -A 5

Repository: onlook-dev/onlook

Length of output: 189


🏁 Script executed:

#!/bin/bash
# Search for TASK_ROUTING in the models package
rg -n "TASK_ROUTING" packages/models/ -A 3

Repository: onlook-dev/onlook

Length of output: 455


🏁 Script executed:

#!/bin/bash
# Look at the router.ts file to understand ModelTier and MODEL_TIERS definitions
cat -n packages/models/src/cynthia/router.ts | head -80

Repository: onlook-dev/onlook

Length of output: 2657


🏁 Script executed:

#!/bin/bash
# Look at the LLM models to understand OPENROUTER_MODELS type
cat -n packages/models/src/llm/*.ts | grep -A 50 "OPENROUTER_MODELS"

Repository: onlook-dev/onlook

Length of output: 1384


🏁 Script executed:

#!/bin/bash
# Check the full context of ModelTier and how it's used
cat -n packages/models/src/cynthia/router.ts

Repository: onlook-dev/onlook

Length of output: 2657


🏁 Script executed:

#!/bin/bash
# Check how initModel uses the model parameter to understand the full type chain
rg -n "initModel" packages/ai/src/prompt/provider.ts -A 15 | head -40

Repository: onlook-dev/onlook

Length of output: 43


🏁 Script executed:

#!/bin/bash
# Find and examine the provider file
find . -name "provider.ts" -path "*/prompt/*" 2>/dev/null

Repository: onlook-dev/onlook

Length of output: 97


🏁 Script executed:

#!/bin/bash
# Read the provider.ts file to understand initModel
cat -n packages/ai/src/prompt/provider.ts

Repository: onlook-dev/onlook

Length of output: 6810


🏁 Script executed:

#!/bin/bash
# Search for initModel function definition
rg -n "export.*initModel\|function initModel" packages/ai/ -A 20

Repository: onlook-dev/onlook

Length of output: 43


🏁 Script executed:

#!/bin/bash
# Search for initModel more broadly
rg -n "initModel" packages/ai/ -B 2 -A 10 | head -50

Repository: onlook-dev/onlook

Length of output: 3466


🏁 Script executed:

#!/bin/bash
# Read the full initModel function from providers.ts
cat -n packages/ai/src/chat/providers.ts | head -100

Repository: onlook-dev/onlook

Length of output: 2247


Type assertion for model lacks compile-time validation.

The ModelTier interface defines model: string, but initModel expects OPENROUTER_MODELS. The cast modelTier.model as OPENROUTER_MODELS at line 103 bypasses type checking. If MODEL_TIERS.ReasonerTier.model is changed to an incompatible string value, TypeScript won't catch it.

Strengthen type safety by making ModelTier enforce the correct model type for each provider, or use a discriminated union that ties model type to provider type.

🤖 Prompt for AI Agents
In packages/ai/src/audit/engine.ts around lines 99 to 124, the code unsafely
casts modelTier.model to OPENROUTER_MODELS; replace this by making the ModelTier
type a discriminated union that ties provider to the exact allowed model type
(for example provider: 'openrouter' -> model: OPENROUTER_MODELS, other providers
-> their respective model enums/strings), update the TASK_ROUTING / MODEL_TIERS
definitions to use that union, then remove the type assertion and pass
modelTier.model directly to initModel so the compiler enforces correctness;
ensure any places that construct MODEL_TIERS are updated to satisfy the new
typed shape.

Comment on lines +62 to +70
// Create audit via tRPC
// Note: This assumes the tRPC client is available on editorEngine
// In production, you'd need to pass this through or use a different mechanism
const audit = await (editorEngine as any).trpc?.audit.create.mutate({
projectId,
targetType,
targetValue,
context: params.context,
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Refactor brittle tRPC client access pattern.

The type assertion (editorEngine as any).trpc violates coding guidelines and creates a fragile runtime dependency. The tRPC client should be passed through the tool constructor or a dedicated service layer rather than assumed to exist on editorEngine.

Based on coding guidelines: "Do not use the any type unless necessary."

🔎 Suggested refactor

Option 1: Pass tRPC client to tool constructor

export class CynthiaAuditTool extends ClientTool {
    constructor(private trpcClient: TRPCClient) {
        super();
    }
    
    async handle(params, editorEngine) {
        const audit = await this.trpcClient.audit.create.mutate({...});
    }
}

Option 2: Use a service locator pattern

import { getApiClient } from '@/lib/api-client';

async handle(params, editorEngine) {
    const api = getApiClient();
    const audit = await api.audit.create.mutate({...});
}
🤖 Prompt for AI Agents
In packages/ai/src/tools/classes/cynthia-audit.ts around lines 62 to 70, the
code uses (editorEngine as any).trpc which violates the "no any" guideline and
creates a brittle runtime dependency; refactor to accept a typed tRPC client
instead (or obtain it from a typed service locator) and use that instance to
call audit.create.mutate: add a strongly-typed trpcClient parameter on the
CynthiaAuditTool constructor (or import/get a typed getApiClient), store it as
this.trpcClient, replace the (editorEngine as any).trpc access with
this.trpcClient.audit.create.mutate(...), remove any casts, and add a runtime
guard that throws a clear error if the client is missing; update the tool's type
signatures and any callers to pass the client and adjust tests/types
accordingly.

Comment on lines +85 to +86
// Preview links table (imported for relations)
import { previewLinks } from './preview-link';
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Move import to the top of the file to avoid confusion.

The previewLinks import is placed at the end of the file after it's used in buildSessionRelations (line 69). While ES modules support this via live bindings for circular dependencies, placing imports at the end of a file is unconventional and confusing. Move it to the imports section at the top.

🔎 Proposed fix
 import { users } from '../user';
 import { audits } from './audit';
+import { previewLinks } from './preview-link';

 // ... rest of file ...

-// Preview links table (imported for relations)
-import { previewLinks } from './preview-link';

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/db/src/schema/cynthia/build-session.ts around lines 85–86, the
previewLinks import is declared at the end of the file after
buildSessionRelations (used at ~line 69); move the import statement for
previewLinks into the top import block with the other module imports so all
dependencies are declared before usage, keeping the same relative path and name
and removing the trailing import to avoid confusion from late-file imports.

Database Layer:
- Add credit_balances schema with cynthia_plan enum (free/starter/pro/agency)
- Add fix_packs schema with fix_pack_type enum (token/layout/component/motion/content)
- Create comprehensive migration with RLS policies
- Add monthly credit reset function
- Export new schemas from cynthia index

Credit Service:
- Implement getOrCreateCreditBalance (auto-creates free tier)
- Implement checkCredits (returns balance, plan, remaining)
- Implement consumeCredits (atomic decrement with validation)
- Implement updatePlan (called on subscription change)
- Implement resetMonthlyCredits (monthly cron job)
- Define CYNTHIA_PLAN_INFO with pricing ($0/$29/$99/$299)

Entitlement:
- Update audit.unlock from stub to real credit check
- Change from publicProcedure to protectedProcedure (auth required)
- Check credits before unlock
- Consume 1 credit on success
- Return detailed error messages (no_credits reason)

Plan Credits (Authoritative):
- Free: 0 credits, 0 unlocks
- Starter: 10 credits, 5 unlocks ($29/mo)
- Pro: 50 credits, 25 unlocks ($99/mo)
- Agency: 200 credits, 100 unlocks ($299/mo)

Migration: 2025_12_19_add_credits_and_fix_packs.sql (NOT applied)

Next: fixPack router, pricing modal, full audit page, Fix Pack UI
Copilot AI review requested due to automatic review settings December 27, 2025 15:16
API Layer:
- Add fixPack router (generate, list, get endpoints)
- Add credits router (getBalance endpoint)
- Update audit.unlock: Real credit checks + consumption
- Change unlock from public to protected (auth required)
- Fix pack generation costs 1 credit
- Unlock costs 1 credit

Fix Pack Router:
- generate: Check credits, generate preview, save to DB
- list: Get user's fix packs for an audit
- get: Get full fix pack details (patch preview)
- Ownership validation on all endpoints
- Phase 4: Preview only, no auto-apply

Credits Router:
- getBalance: Returns plan, total, used, remaining
- Protected procedure (auth required)

Entitlement Logic:
- checkCredits before unlock/fixPack generation
- consumeCredits on success (atomic decrement)
- Return detailed error messages (no_credits reason)
- Race condition handling (double-check after consume)

Background Jobs:
- Document current approach (setTimeout)
- Document BullMQ migration path for Phase 5
- Job invocation sites documented
- Acceptance criteria defined

Wired to root router: fixPack, credits

Next: UI layer, security checks, final report
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (4)
apps/web/client/src/server/api/routers/cynthia/audit.ts (4)

88-98: Missing ownership check in list procedure exposes other users' audits.

The list procedure returns all audits for a projectId without verifying the requesting user has access to that project.


103-136: getTeaser procedure missing ownership check.

Unlike the get procedure, getTeaser doesn't verify the requesting user owns the audit, potentially exposing teaser data to unauthorized users.


181-207: Missing ownership verification in updateStatus procedure.

Any authenticated user can update the status of any audit by ID without ownership verification.


212-246: Missing ownership verification and excessive use of z.any() in saveResults.

  1. Security: Any authenticated user can save results to any audit.
  2. Type safety: Using z.any() for multiple fields bypasses validation and violates coding guidelines about avoiding the any type.
🧹 Nitpick comments (4)
packages/db/src/schema/cynthia/credit-balance.ts (1)

60-66: Consider stronger typing for CYNTHIA_PLAN_CREDITS.

Using Record<string, number> loses type safety. Accessing an invalid plan key (e.g., CYNTHIA_PLAN_CREDITS['invalid']) won't produce a TypeScript error.

🔎 Proposed fix
-export const CYNTHIA_PLAN_CREDITS: Record<string, number> = {
+export const CYNTHIA_PLAN_CREDITS: Record<'free' | 'starter' | 'pro' | 'agency', number> = {
     free: 0,
     starter: 10,
     pro: 50,
     agency: 200,
 };
apps/web/client/src/server/api/routers/cynthia/audit.ts (1)

4-4: Unused import: auditInsertSchema.

The auditInsertSchema is imported but never used in this file.

🔎 Proposed fix
-import { audits, auditInsertSchema } from '@onlook/db/src/schema';
+import { audits } from '@onlook/db/src/schema';
apps/backend/supabase/migrations/2025_12_19_add_credits_and_fix_packs.sql (2)

62-64: Redundant index on user_id column.

The idx_credit_balances_user_id index is redundant because the UNIQUE constraint on user_id (line 35) already creates an implicit unique index.

🔎 Proposed fix
 -- Credit balances indexes
-CREATE INDEX idx_credit_balances_user_id ON credit_balances(user_id);
 CREATE INDEX idx_credit_balances_reset_at ON credit_balances(reset_at);

116-130: reset_monthly_credits function uses SECURITY DEFINER - document the security implications.

SECURITY DEFINER runs with the privileges of the function owner (typically the superuser who created it), bypassing RLS. This is intentional for the cron job but should be restricted to prevent misuse.

Consider adding:

  1. A REVOKE EXECUTE from public roles
  2. Explicit GRANT EXECUTE only to the cron/admin role
REVOKE EXECUTE ON FUNCTION reset_monthly_credits() FROM PUBLIC;
GRANT EXECUTE ON FUNCTION reset_monthly_credits() TO service_role;
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 17476be and 12a8978.

📒 Files selected for processing (6)
  • apps/backend/supabase/migrations/2025_12_19_add_credits_and_fix_packs.sql
  • apps/web/client/src/server/api/routers/cynthia/audit.ts
  • apps/web/client/src/server/api/services/credit-service.ts
  • packages/db/src/schema/cynthia/credit-balance.ts
  • packages/db/src/schema/cynthia/fix-pack.ts
  • packages/db/src/schema/cynthia/index.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Do not use the any type unless necessary

Files:

  • packages/db/src/schema/cynthia/fix-pack.ts
  • packages/db/src/schema/cynthia/credit-balance.ts
  • apps/web/client/src/server/api/routers/cynthia/audit.ts
  • packages/db/src/schema/cynthia/index.ts
  • apps/web/client/src/server/api/services/credit-service.ts
{apps,packages}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Avoid using the any type unless absolutely necessary

Files:

  • packages/db/src/schema/cynthia/fix-pack.ts
  • packages/db/src/schema/cynthia/credit-balance.ts
  • apps/web/client/src/server/api/routers/cynthia/audit.ts
  • packages/db/src/schema/cynthia/index.ts
  • apps/web/client/src/server/api/services/credit-service.ts
apps/web/client/src/server/api/routers/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/server/api/routers/**/*.ts: Place tRPC routers under apps/web/client/src/server/api/routers/**
Use publicProcedure/protectedProcedure from apps/web/client/src/server/api/trpc.ts and validate inputs with Zod
Return plain objects/arrays; rely on SuperJSON for serialization in tRPC procedures

apps/web/client/src/server/api/routers/**/*.ts: Place tRPC routers under src/server/api/routers/**
Use publicProcedure/protectedProcedure from src/server/api/trpc.ts and validate inputs with Zod
Return plain objects/arrays; rely on SuperJSON for serialization

Files:

  • apps/web/client/src/server/api/routers/cynthia/audit.ts
apps/web/client/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/**/*.{ts,tsx}: Use path aliases @/* and ~/* for imports that map to apps/web/client/src/*
Avoid hardcoded user-facing text; use next-intl messages/hooks instead

Use path aliases @/* and ~/* for imports mapping to src/*

Files:

  • apps/web/client/src/server/api/routers/cynthia/audit.ts
  • apps/web/client/src/server/api/services/credit-service.ts
🧠 Learnings (8)
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/server/api/routers/**/*.ts : Use publicProcedure/protectedProcedure from src/server/api/trpc.ts and validate inputs with Zod

Applied to files:

  • apps/web/client/src/server/api/routers/cynthia/audit.ts
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/server/api/routers/**/*.ts : Use publicProcedure/protectedProcedure from apps/web/client/src/server/api/trpc.ts and validate inputs with Zod

Applied to files:

  • apps/web/client/src/server/api/routers/cynthia/audit.ts
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/server/api/root.ts : Export all tRPC routers from apps/web/client/src/server/api/root.ts

Applied to files:

  • apps/web/client/src/server/api/routers/cynthia/audit.ts
  • packages/db/src/schema/cynthia/index.ts
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/server/api/root.ts : Export all tRPC routers from src/server/api/root.ts

Applied to files:

  • apps/web/client/src/server/api/routers/cynthia/audit.ts
  • packages/db/src/schema/cynthia/index.ts
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/server/api/routers/**/*.ts : Place tRPC routers under src/server/api/routers/**

Applied to files:

  • apps/web/client/src/server/api/routers/cynthia/audit.ts
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/server/api/routers/**/*.ts : Place tRPC routers under apps/web/client/src/server/api/routers/**

Applied to files:

  • apps/web/client/src/server/api/routers/cynthia/audit.ts
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/trpc/react.tsx : Keep tRPC React client provider(s) behind a client boundary (ensure this provider file is a client component)

Applied to files:

  • apps/web/client/src/server/api/routers/cynthia/audit.ts
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/trpc/react.tsx : Keep tRPC React client/provider behind a single client boundary in src/trpc/react.tsx

Applied to files:

  • apps/web/client/src/server/api/routers/cynthia/audit.ts
🧬 Code graph analysis (3)
packages/db/src/schema/cynthia/fix-pack.ts (2)
packages/db/src/schema/cynthia/audit.ts (1)
  • audits (16-41)
packages/db/src/schema/user/user.ts (1)
  • users (12-25)
packages/db/src/schema/cynthia/credit-balance.ts (1)
packages/db/src/schema/user/user.ts (1)
  • users (12-25)
apps/web/client/src/server/api/services/credit-service.ts (1)
packages/db/src/schema/cynthia/credit-balance.ts (2)
  • creditBalances (16-37)
  • CYNTHIA_PLAN_CREDITS (61-66)
🔇 Additional comments (10)
packages/db/src/schema/cynthia/fix-pack.ts (1)

1-74: LGTM! Well-structured Drizzle schema.

The schema definition is comprehensive with proper foreign key references, RLS enabled, and Zod validation schemas. The column mappings align with the SQL migration (e.g., applied maps to applied_at).

packages/db/src/schema/cynthia/index.ts (1)

1-5: LGTM! Barrel exports correctly include the new modules.

The index file properly re-exports all Cynthia schema modules including the new credit-balance and fix-pack modules.

packages/db/src/schema/cynthia/credit-balance.ts (1)

1-58: LGTM! Credit balance schema is well-defined.

The schema correctly implements the one-balance-per-user constraint via unique(), has proper foreign key cascades, and enables RLS.

apps/web/client/src/server/api/services/credit-service.ts (1)

149-181: LGTM! Plan info constant is well-structured.

The pricing information is clearly organized with appropriate comments for the cent-based pricing.

apps/web/client/src/server/api/routers/cynthia/audit.ts (4)

252-292: LGTM! unlock procedure correctly implements credit checks.

The procedure now uses protectedProcedure (addressing the previous import issue) and properly checks/consumes credits with appropriate error responses. The race condition between checkCredits and consumeCredits is acknowledged and handled with a fallback response at lines 275-283.


14-54: LGTM! create procedure is well-implemented.

Proper input validation with Zod, ownership set from context, and background processing initiated correctly.


59-83: LGTM! get procedure has proper ownership check.

Correctly verifies audit ownership before returning data.


141-176: LGTM! getFull procedure has proper ownership check.

Correctly verifies ownership and audit completion status.

apps/backend/supabase/migrations/2025_12_19_add_credits_and_fix_packs.sql (2)

1-56: LGTM! Table definitions align with Drizzle schemas.

The enum types, table structures, and foreign key references correctly match the corresponding Drizzle schema definitions in fix-pack.ts and credit-balance.ts.


84-92: No action required—RLS policies are correctly configured.

The backend connects using the postgres superuser account (via SUPABASE_DATABASE_URL), which bypasses RLS entirely per PostgreSQL's design. The RLS policies blocking the authenticated role are correctly configured to prevent users from directly modifying their credit balances; the backend's superuser connection allows the service to perform these operations.

Likely an incorrect or invalid review comment.

Comment on lines 37 to 38
const nextReset = new Date();
nextReset.setMonth(nextReset.getMonth() + 1);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Date arithmetic with setMonth() can produce unexpected results.

Adding 1 month to dates like Jan 31 results in Mar 2/3 (JavaScript Date overflow behavior), not Feb 28.

🔎 Proposed fix for predictable month addition
// Helper function for safe month addition
function addMonths(date: Date, months: number): Date {
    const result = new Date(date);
    const day = result.getDate();
    result.setMonth(result.getMonth() + months);
    // Handle overflow (e.g., Jan 31 + 1 month should be Feb 28, not Mar 3)
    if (result.getDate() !== day) {
        result.setDate(0); // Set to last day of previous month
    }
    return result;
}
🤖 Prompt for AI Agents
In apps/web/client/src/server/api/services/credit-service.ts around lines 37 to
38, adding a month via nextReset.setMonth(nextReset.getMonth() + 1) can overflow
(e.g., Jan 31 -> Mar 3); replace this with a safe month-add helper that: clones
the Date, records the original day, sets the month by adding the offset, and if
the day changed (overflow), adjusts to the last day of the prior month (e.g.,
setDate(0) on the adjusted Date); then use that helper to compute nextReset
instead of calling setMonth directly.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces Cynthia v3.0, a comprehensive AI-powered design audit system that evaluates UI across 13 UDEC (design quality) axes and provides actionable fixes. The implementation spans database schemas, API endpoints, AI integration, UI components, and extensive documentation.

Key Changes

  • Complete UDEC scoring system with 13 design axes (CTX, TYP, CLR, ACC, etc.)
  • Database schemas for audits, build sessions, preview links, credits, and fix packs
  • tRPC API routers for audit management, build sessions, and preview link sharing
  • AI integration with Claude 4.5 Sonnet for design analysis and structured output generation
  • UI components for audit creation, status tracking, and report display
  • Viral "Build My Site" flow with anonymous session support and shareable preview links
  • Credit-based monetization system with plan tiers (free/starter/pro/agency)

Reviewed changes

Copilot reviewed 53 out of 53 changed files in this pull request and generated 17 comments.

Show a summary per file
File Description
packages/models/src/cynthia/* Type definitions for UDEC scoring, reports, and model routing
packages/db/src/schema/cynthia/* Database schemas for audits, sessions, credits, and fix packs
packages/ai/src/audit/engine.ts Core audit engine with AI model integration
packages/ai/src/tools/classes/cynthia-audit.ts AI agent tool for triggering audits
apps/web/client/src/server/api/routers/cynthia/* tRPC routers and background processors
apps/web/client/src/app/preview/[slug]/page.tsx Public preview page for shareable audit results
apps/backend/supabase/migrations/* Database migrations for all Cynthia tables
docs/CYNTHIA*.md Comprehensive architecture and implementation documentation

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +143 to +145
// For Phase 3, URL inputs go directly to audit, ideas are converted to URL (stub)
const targetType = inputType === 'url' ? 'url' : 'url'; // TODO: Convert ideas to URLs in Phase 4
const targetValue = inputType === 'url' ? inputValue : `https://example.com`; // Stub for idea conversion
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

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

The stub implementation for idea-to-URL conversion always returns https://example.com regardless of the idea input. This means all idea-based audits will audit the same placeholder URL instead of the user's actual idea, making the feature non-functional.

Implement proper idea-to-URL conversion logic or remove the 'idea' input type until it's ready.

Suggested change
// For Phase 3, URL inputs go directly to audit, ideas are converted to URL (stub)
const targetType = inputType === 'url' ? 'url' : 'url'; // TODO: Convert ideas to URLs in Phase 4
const targetValue = inputType === 'url' ? inputValue : `https://example.com`; // Stub for idea conversion
// URL inputs go directly to audit; ideas are converted to a search URL based on the idea text
const targetType = 'url';
const targetValue =
inputType === 'url'
? inputValue
: `https://www.google.com/search?q=${encodeURIComponent(inputValue)}`;

Copilot uses AI. Check for mistakes.
Comment on lines +79 to +110
-- Credit balances: Users can view their own balance
CREATE POLICY "credit_balances_select_own_policy" ON credit_balances
FOR SELECT TO authenticated
USING (user_id = auth.uid());

-- Credit balances: No public insert (only system/admin)
CREATE POLICY "credit_balances_insert_policy" ON credit_balances
FOR INSERT TO authenticated
WITH CHECK (false); -- System only

-- Credit balances: No public update (only system/admin)
CREATE POLICY "credit_balances_update_policy" ON credit_balances
FOR UPDATE TO authenticated
USING (false); -- System only

-- Enable RLS on fix_packs
ALTER TABLE fix_packs ENABLE ROW LEVEL SECURITY;

-- Fix packs: Users can view their own fix packs
CREATE POLICY "fix_packs_select_own_policy" ON fix_packs
FOR SELECT TO authenticated
USING (user_id = auth.uid());

-- Fix packs: No public insert (only system)
CREATE POLICY "fix_packs_insert_policy" ON fix_packs
FOR INSERT TO authenticated
WITH CHECK (false); -- System only

-- Fix packs: No public update
CREATE POLICY "fix_packs_update_policy" ON fix_packs
FOR UPDATE TO authenticated
USING (false); -- System only
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

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

The RLS policy for credit_balances prevents all authenticated users from inserting or updating records (WITH CHECK false). However, the service functions in credit-service.ts use the application's database client which bypasses RLS. This creates a security model where only service-level code can modify credits, not direct database access.

Add a comment explaining this is intentional and that all credit operations must go through the service layer, or consider adding a service role policy if the application needs programmatic access.

Suggested change
-- Credit balances: Users can view their own balance
CREATE POLICY "credit_balances_select_own_policy" ON credit_balances
FOR SELECT TO authenticated
USING (user_id = auth.uid());
-- Credit balances: No public insert (only system/admin)
CREATE POLICY "credit_balances_insert_policy" ON credit_balances
FOR INSERT TO authenticated
WITH CHECK (false); -- System only
-- Credit balances: No public update (only system/admin)
CREATE POLICY "credit_balances_update_policy" ON credit_balances
FOR UPDATE TO authenticated
USING (false); -- System only
-- Enable RLS on fix_packs
ALTER TABLE fix_packs ENABLE ROW LEVEL SECURITY;
-- Fix packs: Users can view their own fix packs
CREATE POLICY "fix_packs_select_own_policy" ON fix_packs
FOR SELECT TO authenticated
USING (user_id = auth.uid());
-- Fix packs: No public insert (only system)
CREATE POLICY "fix_packs_insert_policy" ON fix_packs
FOR INSERT TO authenticated
WITH CHECK (false); -- System only
-- Fix packs: No public update
CREATE POLICY "fix_packs_update_policy" ON fix_packs
FOR UPDATE TO authenticated
USING (false); -- System only
-- NOTE: RLS for credit_balances is intentionally restrictive.
-- Authenticated users may only SELECT their own balances; INSERT/UPDATE
-- are blocked (USING/WITH CHECK false) so that only the backend service
-- layer, using a privileged/service-role database client that bypasses RLS,
-- can modify credit balances. All credit mutations must go through the
-- service layer. If direct programmatic DB access is needed in the future,
-- add an explicit service-role policy instead of relaxing these checks.
-- Credit balances: Users can view their own balance
CREATE POLICY "credit_balances_select_own_policy" ON credit_balances
FOR SELECT TO authenticated
USING (user_id = auth.uid());
-- Credit balances: No public insert (only system/admin via service layer)
CREATE POLICY "credit_balances_insert_policy" ON credit_balances
FOR INSERT TO authenticated
WITH CHECK (false); -- System only; see note above about service-layer writes
-- Credit balances: No public update (only system/admin via service layer)
CREATE POLICY "credit_balances_update_policy" ON credit_balances
FOR UPDATE TO authenticated
USING (false); -- System only; see note above about service-layer writes
-- Enable RLS on fix_packs
ALTER TABLE fix_packs ENABLE ROW LEVEL SECURITY;
-- NOTE: As with credit_balances, fix_packs writes are restricted so that
-- only the backend service layer or privileged roles can create/update
-- records; authenticated clients can only read their own fix_packs.
-- Fix packs: Users can view their own fix packs
CREATE POLICY "fix_packs_select_own_policy" ON fix_packs
FOR SELECT TO authenticated
USING (user_id = auth.uid());
-- Fix packs: No public insert (only system via service layer)
CREATE POLICY "fix_packs_insert_policy" ON fix_packs
FOR INSERT TO authenticated
WITH CHECK (false); -- System only; see note above about service-layer writes
-- Fix packs: No public update
CREATE POLICY "fix_packs_update_policy" ON fix_packs
FOR UPDATE TO authenticated
USING (false); -- System only; see note above about service-layer writes

Copilot uses AI. Check for mistakes.
Comment on lines +12 to +20
function generatePreviewSlug(): string {
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
const length = 12; // 12 chars = 62^12 combinations (unguessable)
let slug = '';
for (let i = 0; i < length; i++) {
slug += chars.charAt(Math.floor(Math.random() * chars.length));
}
return slug;
}
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

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

The generatePreviewSlug function uses Math.random() which is not cryptographically secure. For preview links that may contain sensitive audit data, using a predictable random number generator could allow attackers to enumerate or guess preview slugs.

Use crypto.randomBytes() or a similar cryptographically secure random number generator for generating preview slugs.

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,146 @@
-- PHASE 4: Add Credit Balances and Fix Packs
-- Created: 2025-12-19
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

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

The migration filename contains a future date (2025-12-19) but the current date is December 27, 2025. Migration files should use the actual creation date to maintain proper ordering and prevent confusion about when changes were made.

Suggested change
-- Created: 2025-12-19
-- Created: 2025-12-27

Copilot uses AI. Check for mistakes.
Comment on lines +18 to +19
const SYSTEM_BUILD_PROJECT_ID = '00000000-0000-0000-0000-000000000001'; // "Build My Site" system project
const SYSTEM_BUILD_USER_ID = '00000000-0000-0000-0000-000000000002'; // System user for anonymous builds
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

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

The hardcoded system IDs for anonymous build sessions should not use placeholder UUIDs. These constants reference system project and user records that don't exist in the database yet. This will cause foreign key constraint violations when anonymous users create build sessions, as the audit creation will attempt to reference non-existent project and user IDs.

Create proper system records during database migration or use a different approach such as nullable projectId fields for anonymous sessions.

Copilot uses AI. Check for mistakes.
import { streamText, generateObject } from 'ai';
import { z } from 'zod';
import { initModel } from '../prompt/provider';
import { LLMProvider, OPENROUTER_MODELS, TaskRoute, TASK_ROUTING, type AuditInput, type CynthiaReport, type DesignIssue, UDECAxis, IssueSeverity, FixType } from '@onlook/models';
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

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

Unused import LLMProvider.

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,66 @@
import { pgEnum, pgTable, uuid, text, integer, timestamp } from 'drizzle-orm/pg-core';
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

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

Unused import text.

Copilot uses AI. Check for mistakes.
Comment on lines +116 to +121
-- Rationale: Users can change status or claim anonymous sessions
DROP POLICY IF EXISTS "build_sessions_update_owner_policy" ON build_sessions;
CREATE POLICY "build_sessions_update_owner_policy" ON build_sessions
FOR UPDATE
TO authenticated
USING (user_id = auth.uid() OR user_id IS NULL)
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

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

The build_sessions_update_owner_policy allows any authenticated user to update rows where user_id is NULL, which means a malicious user can "claim" any anonymous build session by setting its user_id to their own and then read or manipulate that session’s data. With the Supabase anon key exposed in the browser, an attacker can sign in, enumerate target build_session_ids via related APIs (e.g., from preview links), and issue update calls that pass RLS (user_id IS NULL), effectively hijacking other users’ build sessions. Tighten this policy so only the creator or a server-side service role can associate a session with a user (for example by tying updates to a per-session secret or only allowing user assignment in trusted backend code), and avoid permitting arbitrary updates on user_id IS NULL rows from client-side roles.

Suggested change
-- Rationale: Users can change status or claim anonymous sessions
DROP POLICY IF EXISTS "build_sessions_update_owner_policy" ON build_sessions;
CREATE POLICY "build_sessions_update_owner_policy" ON build_sessions
FOR UPDATE
TO authenticated
USING (user_id = auth.uid() OR user_id IS NULL)
-- Rationale: Authenticated users can only update sessions they own; associating
-- anonymous sessions with a user must be done via trusted backend code
DROP POLICY IF EXISTS "build_sessions_update_owner_policy" ON build_sessions;
CREATE POLICY "build_sessions_update_owner_policy" ON build_sessions
FOR UPDATE
TO authenticated
USING (user_id = auth.uid())

Copilot uses AI. Check for mistakes.
Comment on lines +130 to +134
DROP POLICY IF EXISTS "preview_links_select_by_slug_policy" ON preview_links;
CREATE POLICY "preview_links_select_by_slug_policy" ON preview_links
FOR SELECT
TO anon, authenticated
USING (true);
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

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

The preview_links_select_by_slug_policy grants SELECT on all preview_links rows to both anon and authenticated roles with USING (true), which lets anyone holding the public Supabase anon key enumerate every preview slug and associated build_session_id. This breaks the intended "unguessable slug, no enumeration" security model and allows an attacker to list all share links and then fetch each preview (and, in combination with the build session update policy, potentially take over those sessions). Restrict SELECT so that public roles can only read a single row by a provided slug through a tightly scoped backend endpoint or RPC (and not via unrestricted table SELECT), and ensure expired links and any sensitive fields are filtered server-side.

Copilot uses AI. Check for mistakes.
WHERE reset_at <= now();
END;
$$;

Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

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

The reset_monthly_credits function is declared SECURITY DEFINER and performs an unrestricted UPDATE on all credit_balances rows without checking auth.uid() or caller role, so any caller with execute privileges (e.g., via Supabase RPC using the public anon/authenticated roles) can reset every user’s used credits and shift reset_at. This bypasses the RLS protections on credit_balances and enables unauthorized global modification of billing/credit state, which an attacker could abuse to repeatedly zero out their own usage (and everyone else’s) outside of the intended monthly cycle. Lock this down by revoking EXECUTE from untrusted roles and/or adding explicit checks inside the function to ensure only a trusted service role or scheduled job can invoke it, rather than exposing it to general client-facing roles.

Suggested change
-- Restrict execution of this SECURITY DEFINER function to trusted roles only.
-- By revoking EXECUTE from PUBLIC, client-facing roles (e.g., anon/authenticated)
-- cannot call this function unless explicitly granted elsewhere.
REVOKE EXECUTE ON FUNCTION reset_monthly_credits() FROM PUBLIC;

Copilot uses AI. Check for mistakes.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
apps/web/client/src/server/api/routers/cynthia/fix-pack.ts (1)

153-153: Consider using post-consumption balance for accuracy.

The creditsRemaining calculation uses the pre-consumption check result (creditCheck.remaining - 1). While this works for the current 1-credit consumption, it's fragile and could break if the consumption logic changes.

🔎 More maintainable approach

Option 1: Re-check credits after consumption:

             const consumed = await consumeCredits(userId, 1);
             if (!consumed) {
                 throw new TRPCError({
                     code: 'PAYMENT_REQUIRED',
                     message: 'Insufficient credits',
                 });
             }
+
+            // Get updated balance
+            const updatedBalance = await checkCredits(userId);

             // Generate fix pack preview
             const preview = generateFixPackPreview(audit, input.type);

             // ... (database insert code)

             return {
                 id: fixPack.id,
                 preview: preview.patchPreview,
                 diffSummary: {
                     filesAffected: preview.filesAffected.length,
                     linesChanged: preview.patchPreview.diffs?.reduce(
                         (sum: number, d: any) => sum + (d.linesChanged || 0),
                         0
                     ) || 0,
                 },
-                creditsRemaining: creditCheck.remaining - 1,
+                creditsRemaining: updatedBalance.remaining,
             };

Option 2: Have consumeCredits return the updated balance.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 12a8978 and 75fbb0c.

📒 Files selected for processing (5)
  • apps/web/client/src/server/api/root.ts
  • apps/web/client/src/server/api/routers/cynthia/credits.ts
  • apps/web/client/src/server/api/routers/cynthia/fix-pack.ts
  • apps/web/client/src/server/api/routers/cynthia/index.ts
  • docs/background-jobs.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/client/src/server/api/root.ts
🧰 Additional context used
📓 Path-based instructions (4)
apps/web/client/src/server/api/routers/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/server/api/routers/**/*.ts: Place tRPC routers under apps/web/client/src/server/api/routers/**
Use publicProcedure/protectedProcedure from apps/web/client/src/server/api/trpc.ts and validate inputs with Zod
Return plain objects/arrays; rely on SuperJSON for serialization in tRPC procedures

apps/web/client/src/server/api/routers/**/*.ts: Place tRPC routers under src/server/api/routers/**
Use publicProcedure/protectedProcedure from src/server/api/trpc.ts and validate inputs with Zod
Return plain objects/arrays; rely on SuperJSON for serialization

Files:

  • apps/web/client/src/server/api/routers/cynthia/index.ts
  • apps/web/client/src/server/api/routers/cynthia/credits.ts
  • apps/web/client/src/server/api/routers/cynthia/fix-pack.ts
apps/web/client/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/**/*.{ts,tsx}: Use path aliases @/* and ~/* for imports that map to apps/web/client/src/*
Avoid hardcoded user-facing text; use next-intl messages/hooks instead

Use path aliases @/* and ~/* for imports mapping to src/*

Files:

  • apps/web/client/src/server/api/routers/cynthia/index.ts
  • apps/web/client/src/server/api/routers/cynthia/credits.ts
  • apps/web/client/src/server/api/routers/cynthia/fix-pack.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Do not use the any type unless necessary

Files:

  • apps/web/client/src/server/api/routers/cynthia/index.ts
  • apps/web/client/src/server/api/routers/cynthia/credits.ts
  • apps/web/client/src/server/api/routers/cynthia/fix-pack.ts
{apps,packages}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Avoid using the any type unless absolutely necessary

Files:

  • apps/web/client/src/server/api/routers/cynthia/index.ts
  • apps/web/client/src/server/api/routers/cynthia/credits.ts
  • apps/web/client/src/server/api/routers/cynthia/fix-pack.ts
🧠 Learnings (7)
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/server/api/root.ts : Export all tRPC routers from src/server/api/root.ts

Applied to files:

  • apps/web/client/src/server/api/routers/cynthia/index.ts
  • apps/web/client/src/server/api/routers/cynthia/credits.ts
  • apps/web/client/src/server/api/routers/cynthia/fix-pack.ts
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/server/api/root.ts : Export all tRPC routers from apps/web/client/src/server/api/root.ts

Applied to files:

  • apps/web/client/src/server/api/routers/cynthia/index.ts
  • apps/web/client/src/server/api/routers/cynthia/credits.ts
  • apps/web/client/src/server/api/routers/cynthia/fix-pack.ts
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/server/api/routers/**/*.ts : Place tRPC routers under apps/web/client/src/server/api/routers/**

Applied to files:

  • apps/web/client/src/server/api/routers/cynthia/index.ts
  • apps/web/client/src/server/api/routers/cynthia/credits.ts
  • apps/web/client/src/server/api/routers/cynthia/fix-pack.ts
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/server/api/routers/**/*.ts : Place tRPC routers under src/server/api/routers/**

Applied to files:

  • apps/web/client/src/server/api/routers/cynthia/index.ts
  • apps/web/client/src/server/api/routers/cynthia/credits.ts
  • apps/web/client/src/server/api/routers/cynthia/fix-pack.ts
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/server/api/routers/**/*.ts : Use publicProcedure/protectedProcedure from src/server/api/trpc.ts and validate inputs with Zod

Applied to files:

  • apps/web/client/src/server/api/routers/cynthia/index.ts
  • apps/web/client/src/server/api/routers/cynthia/credits.ts
  • apps/web/client/src/server/api/routers/cynthia/fix-pack.ts
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Prefer importing env from @/env; avoid process.env in client code and guard shared modules with typeof window === 'undefined'

Applied to files:

  • apps/web/client/src/server/api/routers/cynthia/index.ts
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/server/api/routers/**/*.ts : Use publicProcedure/protectedProcedure from apps/web/client/src/server/api/trpc.ts and validate inputs with Zod

Applied to files:

  • apps/web/client/src/server/api/routers/cynthia/credits.ts
  • apps/web/client/src/server/api/routers/cynthia/fix-pack.ts
🧬 Code graph analysis (1)
apps/web/client/src/server/api/routers/cynthia/credits.ts (2)
apps/web/client/src/server/api/trpc.ts (2)
  • createTRPCRouter (89-89)
  • protectedProcedure (131-150)
apps/web/client/src/server/api/services/credit-service.ts (1)
  • checkCredits (61-74)
🪛 LanguageTool
docs/background-jobs.md

[uncategorized] ~137-~137: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...istence across restarts - Retry logic - Rate limiting - Monitoring dashboard

(EN_COMPOUND_ADJECTIVE_INTERNAL)

🔇 Additional comments (5)
docs/background-jobs.md (1)

9-20: Clarify how credits integrate with background job processing.

The PR adds credit consumption for audit operations (per PR objectives: "unlock and fix-pack generation cost 1 credit"), but the background jobs documentation does not explain the credit flow during job execution. Key questions remain unanswered:

  • Are credits consumed before the job begins, during processing, or after completion?
  • What happens if credits are exhausted while an audit is running?
  • Does a failed audit return the credit, or is it consumed regardless?

Since background job processing directly impacts credit consumption tracking and user experience, clarify the integration or cross-reference where this behavior is documented.

apps/web/client/src/server/api/routers/cynthia/credits.ts (1)

9-25: LGTM!

The credits router implementation is clean and follows all coding guidelines. Proper use of protectedProcedure, delegates to the credit service, and returns a plain object as required.

apps/web/client/src/server/api/routers/cynthia/index.ts (1)

1-5: LGTM!

Clean barrel file that properly aggregates all Cynthia sub-routers for export.

apps/web/client/src/server/api/routers/cynthia/fix-pack.ts (2)

160-207: LGTM!

The list query properly validates audit ownership and securely filters fix packs by both auditId and userId.


212-238: LGTM!

The get query properly validates fix pack ownership and returns appropriate error codes.

Comment on lines +18 to +20
function generateFixPackPreview(
auditData: any,
type: 'token' | 'layout' | 'component' | 'motion' | 'content'
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Replace any type with proper typing.

The auditData parameter uses any, which violates the coding guideline to avoid any unless necessary. The audit object should be properly typed using the Drizzle-inferred type from the audits schema.

🔎 Recommended approach

Import the inferred type and use it:

+import type { Audit } from '@onlook/db/src/schema';

 function generateFixPackPreview(
-    auditData: any,
+    auditData: Audit,
     type: 'token' | 'layout' | 'component' | 'motion' | 'content'
 ) {

This provides type safety and better IDE support throughout the function.

Based on coding guidelines requiring avoidance of any types.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function generateFixPackPreview(
auditData: any,
type: 'token' | 'layout' | 'component' | 'motion' | 'content'
import type { Audit } from '@onlook/db/src/schema';
function generateFixPackPreview(
auditData: Audit,
type: 'token' | 'layout' | 'component' | 'motion' | 'content'
🤖 Prompt for AI Agents
In apps/web/client/src/server/api/routers/cynthia/fix-pack.ts around lines
18-20, replace the use of the any type for auditData with the Drizzle-inferred
audits type: import the audits schema and InferModel (or the exported inferred
type) from the module that defines your DB schemas, then change the function
signature to use that inferred type (e.g., Audit = InferModel<typeof audits>)
for auditData so the function has proper typing and IDE/type-checker support.


1. **Audit Processing** (`processBuildSessionAudit`)
- Triggered by: `buildSession.create`
- Location: `apps/web/client/src/server/api/routers/cynthia/build-session-processor.ts:44`
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's locate and read the documentation file
fd "background-jobs.md" -t f

Repository: onlook-dev/onlook

Length of output: 84


🏁 Script executed:

# Read the documentation file to see the content and verify line references
cat -n docs/background-jobs.md | head -150

Repository: onlook-dev/onlook

Length of output: 4975


🏁 Script executed:

# Check if the referenced files exist in the codebase
echo "=== Checking build-session-processor.ts ===" 
fd "build-session-processor.ts" -t f

echo -e "\n=== Checking fix-pack.ts ===" 
fd "fix-pack.ts" -t f

echo -e "\n=== Checking SQL migration file ===" 
fd "2025_12_19_add_credits_and_fix_packs.sql" -t f

echo -e "\n=== Checking directory structure of apps/web/client/src/server/api/routers/cynthia ===" 
fd . "apps/web/client/src/server/api/routers/cynthia" -t f 2>/dev/null | head -20

Repository: onlook-dev/onlook

Length of output: 994


🏁 Script executed:

# Verify the specific line numbers mentioned in documentation
echo "=== Checking build-session-processor.ts around line 44 ===" 
sed -n '40,50p' "apps/web/client/src/server/api/routers/cynthia/build-session-processor.ts"

echo -e "\n=== Checking build-session-processor.ts around line 189 ===" 
sed -n '185,195p' "apps/web/client/src/server/api/routers/cynthia/build-session-processor.ts"

echo -e "\n=== Checking fix-pack.ts around line 77 ===" 
sed -n '73,83p' "apps/web/client/src/server/api/routers/cynthia/fix-pack.ts"

echo -e "\n=== Checking SQL migration file around line 123 ===" 
sed -n '120,130p' "apps/backend/supabase/migrations/2025_12_19_add_credits_and_fix_packs.sql"

Repository: onlook-dev/onlook

Length of output: 1137


🏁 Script executed:

# Check total line counts and get more context
echo "=== build-session-processor.ts total lines ===" 
wc -l "apps/web/client/src/server/api/routers/cynthia/build-session-processor.ts"

echo -e "\n=== fix-pack.ts total lines ===" 
wc -l "apps/web/client/src/server/api/routers/cynthia/fix-pack.ts"

echo -e "\n=== SQL migration file total lines ===" 
wc -l "apps/backend/supabase/migrations/2025_12_19_add_credits_and_fix_packs.sql"

# Get the actual setTimeout invocation in build-session-processor.ts
echo -e "\n=== Searching for setTimeout in build-session-processor.ts ===" 
rg "setTimeout" "apps/web/client/src/server/api/routers/cynthia/build-session-processor.ts" -n -A 2

# Check fix-pack.ts for job generation
echo -e "\n=== Searching for generate function in fix-pack.ts ===" 
rg "export.*generate|fixPack.generate" "apps/web/client/src/server/api/routers/cynthia/fix-pack.ts" -n -A 2

# Check SQL migration for reset_monthly_credits
echo -e "\n=== Searching for CREATE OR REPLACE FUNCTION reset_monthly_credits ===" 
rg "CREATE OR REPLACE FUNCTION reset_monthly_credits" "apps/backend/supabase/migrations/2025_12_19_add_credits_and_fix_packs.sql" -n -B 2 -A 5

Repository: onlook-dev/onlook

Length of output: 894


🏁 Script executed:

# Check fix-pack.ts around line 77 and look for the actual job/function that generates fix packs
echo "=== fix-pack.ts around line 77 ===" 
sed -n '70,85p' "apps/web/client/src/server/api/routers/cynthia/fix-pack.ts"

# Search for setTimeout in fix-pack.ts
echo -e "\n=== Searching for setTimeout in fix-pack.ts ===" 
rg "setTimeout" "apps/web/client/src/server/api/routers/cynthia/fix-pack.ts" -n -A 2

# Look for the main router/procedure definition
echo -e "\n=== Looking for fixPack router or procedure ===" 
rg "fixPack|export.*generate" "apps/web/client/src/server/api/routers/cynthia/fix-pack.ts" -n -B 1 -A 1

# Check what's at the start of build-session-processor.ts around line 44
echo -e "\n=== build-session-processor.ts around line 43-45 ===" 
sed -n '43,45p' "apps/web/client/src/server/api/routers/cynthia/build-session-processor.ts"

Repository: onlook-dev/onlook

Length of output: 2906


Correct inaccurate line number references and clarify fix-pack generation is not a background job.

Several line number references in the documentation are incorrect:

  • Line 97-98: build-session-processor.ts:189 exceeds file length (184 lines total); the actual setTimeout invocation is at line 179
  • Line 17: fix-pack.ts:77 does not invoke a background job; fix-pack generation happens synchronously within the tRPC procedure, not via setTimeout
  • Line 106: The reset_monthly_credits() function starts at line 117, not the context around line 123

Additionally, the documentation incorrectly frames fix-pack generation as a background job "triggered by user request" with an invocation location. In the current implementation, fix packs are generated synchronously during tRPC calls, not asynchronously queued.

🤖 Prompt for AI Agents
In docs/background-jobs.md around line 11, update the inaccurate file/line
references and clarify that fix-pack generation is not a background job: change
the build-session-processor reference from :189 to the actual setTimeout
invocation at
apps/web/client/src/server/api/routers/cynthia/build-session-processor.ts:179;
remove or rephrase the claim that fix-pack.ts:77 triggers an asynchronous
background job and instead state that fix packs are generated synchronously
inside the tRPC procedure (so remove the “triggered by user request”
background-job framing and any location that implies setTimeout); and correct
the reset_monthly_credits() reference to start at the actual line
apps/web/server/src/... (or repo path used in doc) line 117 (not around 123).
Ensure the doc text reflects synchronous fix-pack behavior and accurate line
numbers.

Critical Fix: Resolve RLS policy blocking app writes

Problem:
- Phase 4 migration set credit_balances/fix_packs INSERT/UPDATE to "system-only"
- App code tried to auto-create balances, consume credits, insert fix packs
- Would fail with RLS permission denied in production

Solution (Option A - SECURITY DEFINER):
- Create PostgreSQL functions that run with elevated privileges
- Functions enforce ownership rules internally
- App calls functions instead of direct table operations
- RLS policies remain strict (no privilege escalation)

Migration: 2025_12_19_fix_rls_with_security_definer.sql
- ensure_credit_balance(user_id) → Auto-creates free tier, validates self-access
- consume_credit(user_id, amount) → Atomic decrement with ownership check
- update_user_plan(user_id, plan) → Plan changes with validation
- create_fix_pack_record(...) → Creates fix pack with audit ownership validation
- GRANT EXECUTE to authenticated users

Service Layer Updates:
- credit-service.ts: Call SECURITY DEFINER functions via db.execute(sql`...`)
- fix-pack.ts: Use create_fix_pack_record() instead of direct INSERT

Security:
- Functions validate auth.uid() === p_user_id (cannot access other users)
- Atomic operations (FOR UPDATE locking in consume_credit)
- No service-role secrets exposed to client
- RLS policies unchanged (system-only direct writes)

Verification: Phase 4.1 report follows
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
apps/web/client/src/server/api/routers/cynthia/fix-pack.ts (1)

18-24: Replace any types with proper typing.

Multiple uses of any violate the coding guideline to avoid any unless necessary:

  • Line 19: auditData: any
  • Line 24: fp: any in the find callback
🔎 Recommended approach

Define proper types based on the expected audit structure:

+interface AuditFixPack {
+    type: 'token' | 'layout' | 'component' | 'motion' | 'content';
+    title?: string;
+    description?: string;
+    patchPreview?: Record<string, unknown>;
+    filesAffected?: string[];
+    issuesFixed?: unknown[];
+}
+
+interface AuditData {
+    fixPacks?: AuditFixPack[];
+}
+
 function generateFixPackPreview(
-    auditData: any,
+    auditData: AuditData,
     type: 'token' | 'layout' | 'component' | 'motion' | 'content'
 ) {
     const fixPacksData = auditData.fixPacks || [];
-    const relevantFix = fixPacksData.find((fp: any) => fp.type === type);
+    const relevantFix = fixPacksData.find((fp) => fp.type === type);

Based on coding guidelines requiring avoidance of any types.

🧹 Nitpick comments (1)
apps/web/client/src/server/api/routers/cynthia/fix-pack.ts (1)

146-154: Replace any type in reduce callback.

Line 149 uses any for the diff object, which violates coding guidelines.

🔎 Proposed fix
+interface DiffEntry {
+    file: string;
+    before: string;
+    after: string;
+    linesChanged?: number;
+}
+
 diffSummary: {
     filesAffected: preview.filesAffected.length,
-    linesChanged: preview.patchPreview.diffs?.reduce(
-        (sum: number, d: any) => sum + (d.linesChanged || 0),
+    linesChanged: (preview.patchPreview.diffs as DiffEntry[] | undefined)?.reduce(
+        (sum: number, d: DiffEntry) => sum + (d.linesChanged || 0),
         0
     ) || 0,
 },
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 75fbb0c and e4ddef6.

📒 Files selected for processing (3)
  • apps/backend/supabase/migrations/2025_12_19_fix_rls_with_security_definer.sql
  • apps/web/client/src/server/api/routers/cynthia/fix-pack.ts
  • apps/web/client/src/server/api/services/credit-service.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/client/src/server/api/services/credit-service.ts
🧰 Additional context used
📓 Path-based instructions (4)
apps/web/client/src/server/api/routers/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/server/api/routers/**/*.ts: Place tRPC routers under apps/web/client/src/server/api/routers/**
Use publicProcedure/protectedProcedure from apps/web/client/src/server/api/trpc.ts and validate inputs with Zod
Return plain objects/arrays; rely on SuperJSON for serialization in tRPC procedures

apps/web/client/src/server/api/routers/**/*.ts: Place tRPC routers under src/server/api/routers/**
Use publicProcedure/protectedProcedure from src/server/api/trpc.ts and validate inputs with Zod
Return plain objects/arrays; rely on SuperJSON for serialization

Files:

  • apps/web/client/src/server/api/routers/cynthia/fix-pack.ts
apps/web/client/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/**/*.{ts,tsx}: Use path aliases @/* and ~/* for imports that map to apps/web/client/src/*
Avoid hardcoded user-facing text; use next-intl messages/hooks instead

Use path aliases @/* and ~/* for imports mapping to src/*

Files:

  • apps/web/client/src/server/api/routers/cynthia/fix-pack.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Do not use the any type unless necessary

Files:

  • apps/web/client/src/server/api/routers/cynthia/fix-pack.ts
{apps,packages}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Avoid using the any type unless absolutely necessary

Files:

  • apps/web/client/src/server/api/routers/cynthia/fix-pack.ts
🧠 Learnings (8)
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/server/api/routers/**/*.ts : Use publicProcedure/protectedProcedure from src/server/api/trpc.ts and validate inputs with Zod

Applied to files:

  • apps/web/client/src/server/api/routers/cynthia/fix-pack.ts
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/server/api/routers/**/*.ts : Use publicProcedure/protectedProcedure from apps/web/client/src/server/api/trpc.ts and validate inputs with Zod

Applied to files:

  • apps/web/client/src/server/api/routers/cynthia/fix-pack.ts
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/server/api/root.ts : Export all tRPC routers from apps/web/client/src/server/api/root.ts

Applied to files:

  • apps/web/client/src/server/api/routers/cynthia/fix-pack.ts
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/server/api/routers/**/*.ts : Place tRPC routers under src/server/api/routers/**

Applied to files:

  • apps/web/client/src/server/api/routers/cynthia/fix-pack.ts
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/server/api/root.ts : Export all tRPC routers from src/server/api/root.ts

Applied to files:

  • apps/web/client/src/server/api/routers/cynthia/fix-pack.ts
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/server/api/routers/**/*.ts : Place tRPC routers under apps/web/client/src/server/api/routers/**

Applied to files:

  • apps/web/client/src/server/api/routers/cynthia/fix-pack.ts
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to {apps,packages}/**/*.{ts,tsx} : Avoid using the any type unless absolutely necessary

Applied to files:

  • apps/web/client/src/server/api/routers/cynthia/fix-pack.ts
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to **/*.{ts,tsx} : Do not use the any type unless necessary

Applied to files:

  • apps/web/client/src/server/api/routers/cynthia/fix-pack.ts
🔇 Additional comments (8)
apps/web/client/src/server/api/routers/cynthia/fix-pack.ts (3)

1-12: LGTM on imports and router setup.

The file correctly uses protectedProcedure from ../../trpc and validates inputs with Zod, following the coding guidelines.


160-207: LGTM on list procedure.

The procedure correctly validates ownership before returning fix packs for the audit.


212-238: LGTM on get procedure.

The procedure correctly validates ownership before returning the fix pack details.

apps/backend/supabase/migrations/2025_12_19_fix_rls_with_security_definer.sql (5)

10-65: Well-structured SECURITY DEFINER function with proper ownership check.

The ensure_credit_balance function correctly validates auth.uid() before accessing data and creates a free tier balance if none exists.


67-133: Solid atomic credit consumption with FOR UPDATE locking.

The consume_credit function properly uses FOR UPDATE to lock the row during credit validation and consumption, preventing race conditions.


135-183: LGTM on plan update function.

The function correctly validates ownership and updates the plan with appropriate credit allocation.


186-259: Good dual ownership validation.

The function correctly validates both that the caller owns the request (auth.uid() check) and that they own the referenced audit before creating the fix pack.


265-269: LGTM on grant statements.

Granting EXECUTE to authenticated role is appropriate for these user-facing functions that enforce their own ownership checks.

COMMENT ON FUNCTION ensure_credit_balance(UUID) IS 'Phase 4.1: Auto-creates free tier balance, callable by authenticated users (self only)';
COMMENT ON FUNCTION consume_credit(UUID, INT) IS 'Phase 4.1: Atomically decrements credits with ownership validation';
COMMENT ON FUNCTION update_user_plan(UUID, cynthia_plan) IS 'Phase 4.1: Updates plan tier on subscription change (self only)';
COMMENT ON FUNCTION create_fix_pack_record IS 'Phase 4.1: Creates fix pack with audit ownership validation';
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix incomplete function signature in COMMENT statement.

The COMMENT ON FUNCTION create_fix_pack_record statement is missing the parameter signature, which may cause an error or ambiguity.

🔎 Proposed fix
-COMMENT ON FUNCTION create_fix_pack_record IS 'Phase 4.1: Creates fix pack with audit ownership validation';
+COMMENT ON FUNCTION create_fix_pack_record(UUID, UUID, fix_pack_type, TEXT, TEXT, JSONB, JSONB, JSONB) IS 'Phase 4.1: Creates fix pack with audit ownership validation';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
COMMENT ON FUNCTION create_fix_pack_record IS 'Phase 4.1: Creates fix pack with audit ownership validation';
COMMENT ON FUNCTION create_fix_pack_record(UUID, UUID, fix_pack_type, TEXT, TEXT, JSONB, JSONB, JSONB) IS 'Phase 4.1: Creates fix pack with audit ownership validation';
🤖 Prompt for AI Agents
In apps/backend/supabase/migrations/2025_12_19_fix_rls_with_security_definer.sql
around line 278, the COMMENT ON FUNCTION statement omits the function's
parameter signature; update the COMMENT ON FUNCTION to include the exact
parameter type list that matches the create_fix_pack_record function definition
(e.g., COMMENT ON FUNCTION create_fix_pack_record(param_type1, param_type2, ...)
IS 'Phase 4.1: Creates fix pack with audit ownership validation'), ensuring
types and order exactly match the function declaration and schema if qualified.

Schema & Migration:
- Add apply_runs table with status enum (queued → running → branch_created → pr_opened → checks_running → success/failed)
- Add SECURITY DEFINER functions: create_apply_run_record(), update_apply_run_status()
- RLS policies: users can view own apply runs, system-only INSERT/UPDATE

BullMQ + Redis:
- Install bullmq + ioredis dependencies (requires manual install due to registry issues)
- Create queue-service.ts: applyQueue + repairQueue with concurrency=2
- Idempotency via jobId: apply-{applyRunId}
- Retry policy: 3 attempts with exponential backoff (2s, 4s, 8s)

Apply Pipeline Service:
- processApplyFixPack(): full GitHub PR creation flow
- Safety checks: verify before substring exists exactly once
- Deterministic patch application with rollback on failure
- Logs all operations to apply_runs.logs JSONB

GitHub Integration:
- Use existing @onlook/github package (createInstallationOctokit)
- Create branch: cynthia/fix-{type}-{timestamp}
- Apply patches with safety validation
- Create PR with fix pack metadata + Cynthia attribution

OpenHands Workflow:
- .github/workflows/openhands-repair.yml
- Trigger: label "agent:repair" on PR
- Safety check: only cynthia/* branches
- Whitelist: apps/web/client/**, packages/**
- Blacklist: .env*, *.key, secrets/**, billing/**
- Push repairs to PR branch only (never main)

tRPC API:
- fixPack.apply: queue apply job, return applyRunId
- applyRun.getStatus: poll apply run status with logs
- applyRun.list: list user's apply runs
- applyRun.listByAudit: list apply runs for specific audit
- Export applyRunRouter in root.ts

UI Components:
- ApplyFixPackButton: modal dialog for repo input
- ApplyRunStatusPanel: status display with PR link, logs, timestamps
- Auto-polling every 3s until terminal status

Worker:
- apply-worker.ts: BullMQ processor wiring
- Calls processApplyFixPack() from pipeline service
- Auto-starts with server (not yet wired to startup)

Environment:
- Add REDIS_HOST, REDIS_PORT to env.ts (default: localhost:6379)

Documentation:
- docs/apply-fix-pack-pipeline.md with Mermaid sequence diagram
- Full architecture, API docs, testing guide

Migration: apps/backend/supabase/migrations/2025_12_27_add_apply_runs.sql
(NOT APPLIED - per ARCHITECT instructions)
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 14

♻️ Duplicate comments (1)
apps/web/client/src/server/api/routers/cynthia/fix-pack.ts (1)

19-54: Replace any types with proper typing throughout this function.

Multiple uses of any violate the coding guideline. This includes auditData, fp, and the reduce callback parameter.

🔎 Comprehensive fix
+interface FixPackIssue {
+    title?: string;
+    id?: string;
+}
+
+interface PatchDiff {
+    file: string;
+    before: string;
+    after: string;
+    linesChanged?: number;
+    description?: string;
+}
+
+interface FixPackPreviewData {
+    type: string;
+    title?: string;
+    description?: string;
+    patchPreview?: {
+        diffs?: PatchDiff[];
+    };
+    filesAffected?: string[];
+    issuesFixed?: FixPackIssue[];
+}
+
+interface AuditData {
+    fixPacks?: FixPackPreviewData[];
+}

 function generateFixPackPreview(
-    auditData: any,
+    auditData: AuditData,
     type: 'token' | 'layout' | 'component' | 'motion' | 'content'
 ) {
     const fixPacksData = auditData.fixPacks || [];
-    const relevantFix = fixPacksData.find((fp: any) => fp.type === type);
+    const relevantFix = fixPacksData.find((fp) => fp.type === type);

And at line 150:

                 diffSummary: {
                     filesAffected: preview.filesAffected.length,
                     linesChanged: preview.patchPreview.diffs?.reduce(
-                        (sum: number, d: any) => sum + (d.linesChanged || 0),
+                        (sum: number, d: PatchDiff) => sum + (d.linesChanged || 0),
                         0
                     ) || 0,
                 },

Based on coding guidelines requiring avoidance of any types.

🧹 Nitpick comments (8)
apps/web/client/src/server/api/workers/apply-worker.ts (1)

48-53: Consider adding graceful shutdown handling.

The worker is started but there's no visible shutdown handling. For production resilience, consider returning cleanup functions or handling process signals to gracefully close the worker.

apps/web/client/src/app/_components/apply-fix-pack/status-panel.tsx (1)

64-64: Unused refetch variable.

The refetch function is destructured but never used. Remove it to clean up the code.

🔎 Proposed fix
-    const { data: applyRun, isLoading, refetch } = api.applyRun.getStatus.useQuery(
+    const { data: applyRun, isLoading } = api.applyRun.getStatus.useQuery(
docs/apply-fix-pack-pipeline.md (1)

436-440: Use proper Markdown link syntax for bare URLs.

Per markdownlint, bare URLs should be wrapped in angle brackets or use proper link syntax for better compatibility.

🔎 Proposed fix
 ## References

 - Phase 5 Spec: ARCHITECT approval 2025-12-27
-- BullMQ Docs: https://docs.bullmq.io
-- OpenHands Docs: https://docs.all-hands.dev
-- GitHub API: https://docs.github.com/rest
+- BullMQ Docs: <https://docs.bullmq.io>
+- OpenHands Docs: <https://docs.all-hands.dev>
+- GitHub API: <https://docs.github.com/rest>
apps/web/client/src/app/_components/apply-fix-pack/apply-button.tsx (1)

59-63: Avoid error: any type.

Per coding guidelines, avoid using any unless necessary. Use unknown and narrow the type for safer error handling.

🔎 Proposed fix
-        } catch (error: any) {
+        } catch (error: unknown) {
+            const message = error instanceof Error ? error.message : 'Unknown error';
             toast.error('Failed to apply fix pack', {
-                description: error.message,
+                description: message,
             });
         }
packages/db/src/schema/cynthia/apply-run.ts (1)

86-100: Consider adding refinements to the update schema.

The insert schema has proper refinements for repoOwner and repoName, but the update schema uses only createUpdateSchema without any refinements. If updates to repoOwner or repoName are allowed, they should have the same min(1) validation.

🔎 Optional refinement
-export const applyRunUpdateSchema = createUpdateSchema(applyRuns);
+export const applyRunUpdateSchema = createUpdateSchema(applyRuns, {
+    repoOwner: z.string().min(1).optional(),
+    repoName: z.string().min(1).optional(),
+    status: z.enum([
+        'queued',
+        'running',
+        'branch_created',
+        'pr_opened',
+        'checks_running',
+        'success',
+        'failed',
+    ]).optional(),
+});
apps/web/client/src/server/api/services/apply-pipeline-service.ts (1)

301-308: Consider validating the installation ID format before use.

The createInstallationOctokit call may fail if the installation ID is invalid or incorrectly formatted. Consider adding validation or more descriptive error handling.

🔎 Optional improvement
         const installationId = data.githubInstallationId || process.env.GITHUB_INSTALLATION_ID;
         if (!installationId) {
             throw new Error('GitHub installation ID not configured');
         }

-        const octokit = createInstallationOctokit(installationId);
+        let octokit: Octokit;
+        try {
+            octokit = createInstallationOctokit(installationId);
+        } catch (error: any) {
+            throw new Error(`Failed to create GitHub client: ${error.message}`);
+        }
apps/web/client/src/server/api/routers/cynthia/apply-run.ts (1)

59-90: Consider returning total count for pagination.

The list endpoint supports pagination but doesn't return a total count, making it difficult for clients to implement proper pagination UI.

🔎 Optional enhancement
         .query(async ({ ctx, input }) => {
             const userId = ctx.user.id;
             const { limit = 20, offset = 0 } = input || {};

+            // Get total count for pagination
+            const [{ count }] = await ctx.db
+                .select({ count: sql<number>`count(*)::int` })
+                .from(applyRuns)
+                .where(eq(applyRuns.userId, userId));

             const runs = await ctx.db
                 .select({
                     // ... existing fields
                 })
                 .from(applyRuns)
                 .where(eq(applyRuns.userId, userId))
                 .orderBy(desc(applyRuns.createdAt))
                 .limit(limit)
                 .offset(offset);

-            return runs;
+            return { runs, total: count };
         }),

This would require importing sql from drizzle-orm.

apps/web/client/src/server/api/services/queue-service.ts (1)

185-191: Graceful shutdown doesn't handle errors.

If any close operation fails, the subsequent operations won't execute. Consider wrapping each in try-catch or using Promise.allSettled.

🔎 Proposed fix
 export async function closeQueues() {
-    await applyWorker?.close();
-    await repairWorker?.close();
-    await applyQueue.close();
-    await repairQueue.close();
-    await redisClient.quit();
+    const results = await Promise.allSettled([
+        applyWorker?.close(),
+        repairWorker?.close(),
+        applyQueue.close(),
+        repairQueue.close(),
+        redisClient.quit(),
+    ]);
+
+    const failures = results.filter((r) => r.status === 'rejected');
+    if (failures.length > 0) {
+        console.error('[Queue Service] Some resources failed to close:', failures);
+    }
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e4ddef6 and cfb0f58.

📒 Files selected for processing (16)
  • .github/workflows/openhands-repair.yml
  • apps/backend/supabase/migrations/2025_12_27_add_apply_runs.sql
  • apps/web/client/src/app/_components/apply-fix-pack/apply-button.tsx
  • apps/web/client/src/app/_components/apply-fix-pack/index.ts
  • apps/web/client/src/app/_components/apply-fix-pack/status-panel.tsx
  • apps/web/client/src/env.ts
  • apps/web/client/src/server/api/root.ts
  • apps/web/client/src/server/api/routers/cynthia/apply-run.ts
  • apps/web/client/src/server/api/routers/cynthia/fix-pack.ts
  • apps/web/client/src/server/api/routers/cynthia/index.ts
  • apps/web/client/src/server/api/services/apply-pipeline-service.ts
  • apps/web/client/src/server/api/services/queue-service.ts
  • apps/web/client/src/server/api/workers/apply-worker.ts
  • docs/apply-fix-pack-pipeline.md
  • packages/db/src/schema/cynthia/apply-run.ts
  • packages/db/src/schema/cynthia/index.ts
✅ Files skipped from review due to trivial changes (1)
  • apps/web/client/src/app/_components/apply-fix-pack/index.ts
🧰 Additional context used
📓 Path-based instructions (9)
apps/web/client/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/**/*.{ts,tsx}: Use path aliases @/* and ~/* for imports that map to apps/web/client/src/*
Avoid hardcoded user-facing text; use next-intl messages/hooks instead

Use path aliases @/* and ~/* for imports mapping to src/*

Files:

  • apps/web/client/src/server/api/workers/apply-worker.ts
  • apps/web/client/src/app/_components/apply-fix-pack/apply-button.tsx
  • apps/web/client/src/app/_components/apply-fix-pack/status-panel.tsx
  • apps/web/client/src/server/api/routers/cynthia/apply-run.ts
  • apps/web/client/src/server/api/routers/cynthia/index.ts
  • apps/web/client/src/env.ts
  • apps/web/client/src/server/api/routers/cynthia/fix-pack.ts
  • apps/web/client/src/server/api/services/queue-service.ts
  • apps/web/client/src/server/api/services/apply-pipeline-service.ts
  • apps/web/client/src/server/api/root.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Do not use the any type unless necessary

Files:

  • apps/web/client/src/server/api/workers/apply-worker.ts
  • apps/web/client/src/app/_components/apply-fix-pack/apply-button.tsx
  • packages/db/src/schema/cynthia/apply-run.ts
  • packages/db/src/schema/cynthia/index.ts
  • apps/web/client/src/app/_components/apply-fix-pack/status-panel.tsx
  • apps/web/client/src/server/api/routers/cynthia/apply-run.ts
  • apps/web/client/src/server/api/routers/cynthia/index.ts
  • apps/web/client/src/env.ts
  • apps/web/client/src/server/api/routers/cynthia/fix-pack.ts
  • apps/web/client/src/server/api/services/queue-service.ts
  • apps/web/client/src/server/api/services/apply-pipeline-service.ts
  • apps/web/client/src/server/api/root.ts
{apps,packages}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Avoid using the any type unless absolutely necessary

Files:

  • apps/web/client/src/server/api/workers/apply-worker.ts
  • apps/web/client/src/app/_components/apply-fix-pack/apply-button.tsx
  • packages/db/src/schema/cynthia/apply-run.ts
  • packages/db/src/schema/cynthia/index.ts
  • apps/web/client/src/app/_components/apply-fix-pack/status-panel.tsx
  • apps/web/client/src/server/api/routers/cynthia/apply-run.ts
  • apps/web/client/src/server/api/routers/cynthia/index.ts
  • apps/web/client/src/env.ts
  • apps/web/client/src/server/api/routers/cynthia/fix-pack.ts
  • apps/web/client/src/server/api/services/queue-service.ts
  • apps/web/client/src/server/api/services/apply-pipeline-service.ts
  • apps/web/client/src/server/api/root.ts
apps/web/client/src/app/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/app/**/*.tsx: Default to Server Components; add 'use client' when using events, state/effects, browser APIs, or client‑only libraries
Do not use process.env in client code; import env from @/env instead

Avoid hardcoded user-facing text; use next-intl messages/hooks

Files:

  • apps/web/client/src/app/_components/apply-fix-pack/apply-button.tsx
  • apps/web/client/src/app/_components/apply-fix-pack/status-panel.tsx
apps/web/client/src/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/**/*.tsx: Create MobX store instances with useState(() => new Store()) for stable references across renders
Keep the active MobX store in a useRef and perform async cleanup with setTimeout(() => storeRef.current?.clear(), 0) to avoid route-change races
Avoid useMemo for creating MobX store instances
Avoid putting the MobX store instance in effect dependency arrays if it causes loops; split concerns by domain

apps/web/client/src/**/*.tsx: Create MobX store instances with useState(() => new Store()) for stable identities across renders
Keep the active MobX store in a useRef and clean up asynchronously with setTimeout(() => storeRef.current?.clear(), 0)
Do not use useMemo to create MobX stores
Avoid placing MobX store instances in effect dependency arrays if it causes loops; split concerns instead
observer components must be client components; place a single client boundary at the feature entry; child observers need not repeat 'use client'

Files:

  • apps/web/client/src/app/_components/apply-fix-pack/apply-button.tsx
  • apps/web/client/src/app/_components/apply-fix-pack/status-panel.tsx
apps/web/client/src/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Default to Server Components; add 'use client' only when using events, state/effects, browser APIs, or client-only libs

Files:

  • apps/web/client/src/app/_components/apply-fix-pack/apply-button.tsx
  • apps/web/client/src/app/_components/apply-fix-pack/status-panel.tsx
apps/web/client/src/server/api/routers/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/server/api/routers/**/*.ts: Place tRPC routers under apps/web/client/src/server/api/routers/**
Use publicProcedure/protectedProcedure from apps/web/client/src/server/api/trpc.ts and validate inputs with Zod
Return plain objects/arrays; rely on SuperJSON for serialization in tRPC procedures

apps/web/client/src/server/api/routers/**/*.ts: Place tRPC routers under src/server/api/routers/**
Use publicProcedure/protectedProcedure from src/server/api/trpc.ts and validate inputs with Zod
Return plain objects/arrays; rely on SuperJSON for serialization

Files:

  • apps/web/client/src/server/api/routers/cynthia/apply-run.ts
  • apps/web/client/src/server/api/routers/cynthia/index.ts
  • apps/web/client/src/server/api/routers/cynthia/fix-pack.ts
apps/web/client/src/env.ts

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/env.ts: Define and validate environment variables via @t3-oss/env-nextjs in apps/web/client/src/env.ts
Expose browser environment variables with NEXT_PUBLIC_* and declare them in the client schema

apps/web/client/src/env.ts: Define and validate environment variables in src/env.ts via @t3-oss/env-nextjs
Expose browser environment variables with NEXT_PUBLIC_* and declare them in the client schema

Files:

  • apps/web/client/src/env.ts
apps/web/client/src/server/api/root.ts

📄 CodeRabbit inference engine (AGENTS.md)

Export all tRPC routers from apps/web/client/src/server/api/root.ts

Export all tRPC routers from src/server/api/root.ts

Files:

  • apps/web/client/src/server/api/root.ts
🧠 Learnings (19)
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/server/api/root.ts : Export all tRPC routers from src/server/api/root.ts

Applied to files:

  • packages/db/src/schema/cynthia/index.ts
  • apps/web/client/src/server/api/routers/cynthia/apply-run.ts
  • apps/web/client/src/server/api/routers/cynthia/index.ts
  • apps/web/client/src/server/api/routers/cynthia/fix-pack.ts
  • apps/web/client/src/server/api/root.ts
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/server/api/root.ts : Export all tRPC routers from apps/web/client/src/server/api/root.ts

Applied to files:

  • packages/db/src/schema/cynthia/index.ts
  • apps/web/client/src/server/api/routers/cynthia/apply-run.ts
  • apps/web/client/src/server/api/routers/cynthia/index.ts
  • apps/web/client/src/server/api/routers/cynthia/fix-pack.ts
  • apps/web/client/src/server/api/root.ts
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/app/**/*.tsx : Default to Server Components; add 'use client' when using events, state/effects, browser APIs, or client‑only libraries

Applied to files:

  • apps/web/client/src/app/_components/apply-fix-pack/status-panel.tsx
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/trpc/react.tsx : Keep tRPC React client provider(s) behind a client boundary (ensure this provider file is a client component)

Applied to files:

  • apps/web/client/src/app/_components/apply-fix-pack/status-panel.tsx
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/server/api/routers/**/*.ts : Use publicProcedure/protectedProcedure from src/server/api/trpc.ts and validate inputs with Zod

Applied to files:

  • apps/web/client/src/server/api/routers/cynthia/apply-run.ts
  • apps/web/client/src/server/api/routers/cynthia/index.ts
  • apps/web/client/src/server/api/routers/cynthia/fix-pack.ts
  • apps/web/client/src/server/api/root.ts
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/server/api/routers/**/*.ts : Use publicProcedure/protectedProcedure from apps/web/client/src/server/api/trpc.ts and validate inputs with Zod

Applied to files:

  • apps/web/client/src/server/api/routers/cynthia/apply-run.ts
  • apps/web/client/src/server/api/routers/cynthia/index.ts
  • apps/web/client/src/server/api/routers/cynthia/fix-pack.ts
  • apps/web/client/src/server/api/root.ts
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/server/api/routers/**/*.ts : Place tRPC routers under apps/web/client/src/server/api/routers/**

Applied to files:

  • apps/web/client/src/server/api/routers/cynthia/apply-run.ts
  • apps/web/client/src/server/api/routers/cynthia/index.ts
  • apps/web/client/src/server/api/routers/cynthia/fix-pack.ts
  • apps/web/client/src/server/api/root.ts
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/server/api/routers/**/*.ts : Place tRPC routers under src/server/api/routers/**

Applied to files:

  • apps/web/client/src/server/api/routers/cynthia/apply-run.ts
  • apps/web/client/src/server/api/routers/cynthia/index.ts
  • apps/web/client/src/server/api/routers/cynthia/fix-pack.ts
  • apps/web/client/src/server/api/root.ts
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Prefer importing env from @/env; avoid process.env in client code and guard shared modules with typeof window === 'undefined'

Applied to files:

  • apps/web/client/src/server/api/routers/cynthia/index.ts
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/env.ts : Expose browser environment variables with NEXT_PUBLIC_* and declare them in the client schema

Applied to files:

  • apps/web/client/src/env.ts
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/env.ts : Define and validate environment variables via t3-oss/env-nextjs in apps/web/client/src/env.ts

Applied to files:

  • apps/web/client/src/env.ts
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/env.ts : Define and validate environment variables in src/env.ts via t3-oss/env-nextjs

Applied to files:

  • apps/web/client/src/env.ts
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/trpc/helpers.ts : If reading process.env in server-only helpers, limit to deployment variables (e.g., VERCEL_URL, PORT)

Applied to files:

  • apps/web/client/src/env.ts
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/app/**/*.tsx : Do not use process.env in client code; import env from @/env instead

Applied to files:

  • apps/web/client/src/env.ts
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/next.config.ts : Import ./src/env in next.config.ts to enforce env validation at build time

Applied to files:

  • apps/web/client/src/env.ts
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/next.config.ts : Import ./src/env in Next.js config to enforce env validation at build time

Applied to files:

  • apps/web/client/src/env.ts
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to {apps,packages}/**/*.{ts,tsx} : Avoid using the any type unless absolutely necessary

Applied to files:

  • apps/web/client/src/server/api/routers/cynthia/fix-pack.ts
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to **/*.{ts,tsx} : Do not use the any type unless necessary

Applied to files:

  • apps/web/client/src/server/api/routers/cynthia/fix-pack.ts
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/server/api/routers/**/*.ts : Return plain objects/arrays; rely on SuperJSON for serialization in tRPC procedures

Applied to files:

  • apps/web/client/src/server/api/root.ts
🧬 Code graph analysis (4)
apps/web/client/src/server/api/workers/apply-worker.ts (2)
apps/web/client/src/server/api/services/queue-service.ts (2)
  • ApplyFixPackJobData (23-30)
  • startApplyWorker (92-118)
apps/web/client/src/server/api/services/apply-pipeline-service.ts (1)
  • processApplyFixPack (267-367)
packages/db/src/schema/cynthia/apply-run.ts (3)
packages/db/src/schema/user/user.ts (1)
  • users (12-25)
packages/db/src/schema/cynthia/audit.ts (1)
  • audits (16-41)
packages/db/src/schema/cynthia/fix-pack.ts (1)
  • fixPacks (18-49)
apps/web/client/src/server/api/routers/cynthia/apply-run.ts (2)
apps/web/client/src/server/api/trpc.ts (2)
  • createTRPCRouter (89-89)
  • protectedProcedure (131-150)
packages/db/src/schema/cynthia/apply-run.ts (1)
  • applyRuns (27-67)
apps/web/client/src/server/api/routers/cynthia/fix-pack.ts (5)
apps/web/client/src/server/api/trpc.ts (2)
  • createTRPCRouter (89-89)
  • protectedProcedure (131-150)
packages/db/src/schema/cynthia/audit.ts (1)
  • audits (16-41)
apps/web/client/src/server/api/services/credit-service.ts (2)
  • checkCredits (50-63)
  • consumeCredits (70-85)
packages/db/src/schema/cynthia/fix-pack.ts (1)
  • fixPacks (18-49)
apps/web/client/src/server/api/services/queue-service.ts (1)
  • queueApplyFixPack (158-166)
🪛 actionlint (1.7.9)
.github/workflows/openhands-repair.yml

28-28: "github.event.pull_request.head.ref" is potentially untrusted. avoid using it directly in inline scripts. instead, pass it through an environment variable. see https://docs.github.com/en/actions/reference/security/secure-use#good-practices-for-mitigating-script-injection-attacks for more details

(expression)


98-98: "github.event.pull_request.head.ref" is potentially untrusted. avoid using it directly in inline scripts. instead, pass it through an environment variable. see https://docs.github.com/en/actions/reference/security/secure-use#good-practices-for-mitigating-script-injection-attacks for more details

(expression)

🪛 markdownlint-cli2 (0.18.1)
docs/apply-fix-pack-pipeline.md

438-438: Bare URL used

(MD034, no-bare-urls)


439-439: Bare URL used

(MD034, no-bare-urls)


440-440: Bare URL used

(MD034, no-bare-urls)

🔇 Additional comments (18)
.github/workflows/openhands-repair.yml (1)

1-17: LGTM!

The workflow trigger configuration and permissions are appropriate for this use case. The label-based trigger with explicit condition check is a good pattern for controlled automation.

packages/db/src/schema/cynthia/index.ts (1)

1-6: LGTM!

Clean barrel exports for Cynthia schema modules.

apps/web/client/src/server/api/root.ts (1)

49-54: LGTM!

New Cynthia-related routers are properly registered in the appRouter. As per coding guidelines, all tRPC routers are exported from this file.

apps/web/client/src/server/api/routers/cynthia/index.ts (1)

1-6: LGTM!

Barrel exports for Cynthia routers follow the established pattern. As per coding guidelines, routers are placed under src/server/api/routers/**.

apps/web/client/src/app/_components/apply-fix-pack/apply-button.tsx (1)

26-64: LGTM on component logic!

Clean implementation with proper state management, input validation, loading states, and callback handling. The mutation flow is well-structured with appropriate user feedback via toasts.

apps/web/client/src/env.ts (1)

64-66: LGTM! Redis configuration follows env schema patterns with proper string-to-number conversion.

Server-side Redis configuration with sensible defaults for local development, correctly not exposed to client. The REDIS_PORT string value from the environment is properly converted to a number in queue-service.ts using parseInt() before being passed to the Redis connection configuration.

packages/db/src/schema/cynthia/apply-run.ts (3)

1-7: LGTM! Schema imports and structure are well-organized.

The imports are appropriate for the schema definition, and the references to related tables (users, audits, fixPacks) are correctly established.


13-21: Enum definition aligns with the SQL migration.

The applyRunStatus enum correctly defines all status states matching the Phase 5 lifecycle.


27-67: Table schema is well-structured with proper constraints.

The table definition includes appropriate FK references with cascade behavior, RLS is enabled, and the nullable fields (branch, prNumber, prUrl, logs, error) correctly reflect the progressive population during the apply-run lifecycle.

apps/web/client/src/server/api/services/apply-pipeline-service.ts (2)

340-344: Property name inconsistency: pr.url should be pr.html_url.

At line 260, the function correctly returns url: pr.html_url, but at line 343, the code uses prUrl: pr.url. The pr object returned from createPullRequest has a url property (line 260), so this is actually correct. However, the naming is confusing - consider renaming the return property for clarity.


118-146: Good implementation of safety checks for patch application.

The applyPatchToFile function includes robust safety checks:

  1. Verifies the "before" substring exists in the file
  2. Ensures exactly one occurrence to prevent ambiguous replacements
  3. Uses deterministic replace for the patch

This prevents accidental modifications when file content has changed.

apps/web/client/src/server/api/routers/cynthia/apply-run.ts (2)

12-54: Well-structured router with proper authorization.

The getStatus endpoint correctly validates UUID input, fetches the record, and performs ownership verification before returning data. The error handling uses appropriate tRPC error codes (NOT_FOUND, FORBIDDEN).

Based on learnings, this follows the coding guidelines for using protectedProcedure and Zod validation.


95-123: LGTM! Proper scoping by both audit and user.

The listByAudit endpoint correctly uses and() to filter by both auditId and userId, ensuring users can only see apply runs for audits they own.

apps/web/client/src/server/api/routers/cynthia/fix-pack.ts (1)

245-311: LGTM! Apply endpoint properly validates ownership and queues job.

The apply mutation correctly:

  1. Validates fix pack existence and ownership
  2. Creates an apply run record via SECURITY DEFINER function
  3. Queues the job with proper data
  4. Returns the run ID and job ID for client tracking
apps/web/client/src/server/api/services/queue-service.ts (2)

158-166: Job option removeOnComplete: true overrides queue defaults.

The queueApplyFixPack function sets removeOnComplete: true, which overrides the queue's default of keeping completed jobs for 24 hours. This may be intentional for idempotency, but the comment at line 160 only mentions idempotency, not the removal behavior.

Is the immediate removal of completed jobs intentional? If so, consider adding a comment explaining why this differs from the queue defaults.


47-84: Queue configurations are well-structured with sensible defaults.

Both queues have appropriate retry strategies with exponential backoff, and reasonable retention policies for completed/failed jobs. The 5-minute lock duration for apply and 10-minute for repair appropriately accounts for the expected operation times.

apps/backend/supabase/migrations/2025_12_27_add_apply_runs.sql (2)

67-83: RLS policies are correctly restrictive.

The policies correctly:

  • Allow SELECT only for authenticated users viewing their own records
  • Block direct INSERT/UPDATE, forcing use of SECURITY DEFINER functions

This enforces the ownership model consistently.


56-61: Good index coverage for query patterns.

The indexes cover the primary query patterns:

  • user_id for ownership filtering
  • status for status-based queries
  • created_at for ordering
  • audit_id and fix_pack_id for relationship lookups

Comment on lines +26 to +37
- name: Safety Check - Verify Cynthia PR
id: safety
run: |
# Verify this is a Cynthia-generated PR (branch starts with 'cynthia/')
BRANCH="${{ github.event.pull_request.head.ref }}"
if [[ ! "$BRANCH" =~ ^cynthia/ ]]; then
echo "ERROR: This workflow only runs on Cynthia-generated PRs (branch must start with 'cynthia/')"
echo "is_cynthia_pr=false" >> $GITHUB_OUTPUT
exit 1
fi
echo "is_cynthia_pr=true" >> $GITHUB_OUTPUT
echo "Verified Cynthia PR: $BRANCH"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Security: Script injection vulnerability via untrusted branch name.

The github.event.pull_request.head.ref is directly interpolated into the shell script, which allows script injection attacks. An attacker could create a branch name containing shell metacharacters to execute arbitrary commands.

🔎 Proposed fix using environment variable
      - name: Safety Check - Verify Cynthia PR
        id: safety
+       env:
+         BRANCH: ${{ github.event.pull_request.head.ref }}
        run: |
          # Verify this is a Cynthia-generated PR (branch starts with 'cynthia/')
-         BRANCH="${{ github.event.pull_request.head.ref }}"
          if [[ ! "$BRANCH" =~ ^cynthia/ ]]; then
            echo "ERROR: This workflow only runs on Cynthia-generated PRs (branch must start with 'cynthia/')"
            echo "is_cynthia_pr=false" >> $GITHUB_OUTPUT
            exit 1
          fi
          echo "is_cynthia_pr=true" >> $GITHUB_OUTPUT
          echo "Verified Cynthia PR: $BRANCH"
🧰 Tools
🪛 actionlint (1.7.9)

28-28: "github.event.pull_request.head.ref" is potentially untrusted. avoid using it directly in inline scripts. instead, pass it through an environment variable. see https://docs.github.com/en/actions/reference/security/secure-use#good-practices-for-mitigating-script-injection-attacks for more details

(expression)

🤖 Prompt for AI Agents
.github/workflows/openhands-repair.yml lines 26-37: the workflow directly
interpolates github.event.pull_request.head.ref into shell output and logs,
which can allow script-injection via malicious branch names; instead, read the
ref into a raw variable, validate it against a strict whitelist (e.g.,
^cynthia/[A-Za-z0-9._-]+$) and reject non-matching values, then use only the
validated/sanitized variable for setting GITHUB_OUTPUT and logging; ensure no
unvalidated variable is passed to commands or exported.

Comment on lines +39 to +49
- name: Get CI failure logs
id: ci_logs
run: |
# Get the latest CI run status and logs
# This is a simplified version - in production, fetch actual CI logs
echo "Fetching CI logs for PR #${{ github.event.pull_request.number }}"
# Store logs for OpenHands context
mkdir -p /tmp/ci-logs
echo "Build failed - type errors in src/components" > /tmp/ci-logs/build-error.log
echo "CI_LOGS_PATH=/tmp/ci-logs" >> $GITHUB_OUTPUT
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Incomplete implementation: CI logs are hardcoded placeholders.

The CI failure logs are static dummy data, which means the OpenHands agent will receive no useful context about actual failures. Consider using the GitHub API to fetch real workflow run logs.

🔎 Example implementation using GitHub CLI
      - name: Get CI failure logs
        id: ci_logs
+       env:
+         GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
-         # Get the latest CI run status and logs
-         # This is a simplified version - in production, fetch actual CI logs
-         echo "Fetching CI logs for PR #${{ github.event.pull_request.number }}"
-
-         # Store logs for OpenHands context
          mkdir -p /tmp/ci-logs
-         echo "Build failed - type errors in src/components" > /tmp/ci-logs/build-error.log
+         
+         # Get the latest failed workflow run for this PR's head SHA
+         HEAD_SHA="${{ github.event.pull_request.head.sha }}"
+         
+         # Fetch failed check runs and their logs
+         gh api "/repos/${{ github.repository }}/commits/${HEAD_SHA}/check-runs" \
+           --jq '.check_runs[] | select(.conclusion == "failure") | {name, output}' \
+           > /tmp/ci-logs/check-runs.json 2>&1 || echo "No check runs found"
+         
+         # Fetch workflow run logs if available
+         gh run list --commit "$HEAD_SHA" --status failure --json databaseId,name \
+           --jq '.[0].databaseId' | xargs -I {} gh run view {} --log-failed \
+           > /tmp/ci-logs/build-error.log 2>&1 || echo "No workflow logs found"
+         
          echo "CI_LOGS_PATH=/tmp/ci-logs" >> $GITHUB_OUTPUT

Would you like me to open an issue to track the full implementation of CI log retrieval?

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Get CI failure logs
id: ci_logs
run: |
# Get the latest CI run status and logs
# This is a simplified version - in production, fetch actual CI logs
echo "Fetching CI logs for PR #${{ github.event.pull_request.number }}"
# Store logs for OpenHands context
mkdir -p /tmp/ci-logs
echo "Build failed - type errors in src/components" > /tmp/ci-logs/build-error.log
echo "CI_LOGS_PATH=/tmp/ci-logs" >> $GITHUB_OUTPUT
- name: Get CI failure logs
id: ci_logs
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
mkdir -p /tmp/ci-logs
# Get the latest failed workflow run for this PR's head SHA
HEAD_SHA="${{ github.event.pull_request.head.sha }}"
# Fetch failed check runs and their logs
gh api "/repos/${{ github.repository }}/commits/${HEAD_SHA}/check-runs" \
--jq '.check_runs[] | select(.conclusion == "failure") | {name, output}' \
> /tmp/ci-logs/check-runs.json 2>&1 || echo "No check runs found"
# Fetch workflow run logs if available
gh run list --commit "$HEAD_SHA" --status failure --json databaseId,name \
--jq '.[0].databaseId' | xargs -I {} gh run view {} --log-failed \
> /tmp/ci-logs/build-error.log 2>&1 || echo "No workflow logs found"
echo "CI_LOGS_PATH=/tmp/ci-logs" >> $GITHUB_OUTPUT

Comment on lines +51 to +80
- name: Run OpenHands Repair Agent
uses: docker://ghcr.io/all-hands-ai/openhands:latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
with:
args: |
--task "Fix the CI failures in this PR.
Context:
- PR #${{ github.event.pull_request.number }}: ${{ github.event.pull_request.title }}
- Branch: ${{ github.event.pull_request.head.ref }}
- CI logs: ${{ steps.ci_logs.outputs.CI_LOGS_PATH }}
Safety rules:
- Only modify files in: apps/web/client/**, packages/**
- DO NOT modify: .env*, *.key, secrets/**, billing/**
- DO NOT create new files unless absolutely necessary
- Commit fixes directly to this branch: ${{ github.event.pull_request.head.ref }}
- Use commit message: 'fix(openhands): Resolve CI failures [automated]'
Steps:
1. Read the CI failure logs
2. Identify the root cause
3. Make minimal fixes to resolve the failures
4. Run tests to verify the fix
5. Commit the changes
Do NOT push to main. Only commit to the current PR branch."
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

OpenHands Docker image entrypoint args format GitHub Actions

💡 Result:

Short answer — use runs.args as an array of strings (each arg is a separate item) and runs.entrypoint as a string to override ENTRYPOINT. When you consume a container action in a workflow you pass entrypoint/args under with:. Example snippets:

  • action metadata (action.yml):
    runs:
    using: "docker"
    image: "Dockerfile"
    entrypoint: "my-entrypoint.sh" # optional override
    args:

    • "first-arg"
    • "second-arg"
  • workflow step (uses a published container action):

  • name: Run OpenHands container action
    uses: docker://openhands/my-image:latest
    with:
    entrypoint: "my-entrypoint.sh"
    args: |
    first-arg
    second-arg

Notes:

  • runs.args replaces the Dockerfile CMD and are passed to the container ENTRYPOINT as separate arguments. [1]
  • If your Docker ENTRYPOINT uses exec form, args are not run through a shell (no env var substitution); use a shell form or wrap with sh -c / entrypoint script if you need variable substitution. [2]

Sources: GitHub Actions metadata / Dockerfile support docs. [1][2]

References
[1] Metadata syntax reference — runs.entrypoint / runs.args.
[2] Dockerfile support for GitHub Actions — ENTRYPOINT / args behavior.


🏁 Script executed:

# Check if there are other references to OpenHands in the repository
rg "openhands" -i --type yaml --type yml --type md -A 3 -B 1

Repository: onlook-dev/onlook

Length of output: 88


🏁 Script executed:

# Look for documentation or examples of how this action should be used
find . -type f \( -name "*.md" -o -name "README*" \) | head -20

Repository: onlook-dev/onlook

Length of output: 606


🏁 Script executed:

# Search for OpenHands references in YAML files
rg "openhands" -i --type yaml -A 3 -B 1

Repository: onlook-dev/onlook

Length of output: 43


🏁 Script executed:

# Check AGENTS.md and CLAUDE.md for documentation about OpenHands usage
head -100 AGENTS.md

Repository: onlook-dev/onlook

Length of output: 4270


🌐 Web query:

all-hands-ai/openhands Docker image GitHub container registry documentation

💡 Result:

Summary — where to find docs and how to pull the image

  • Official repo README (runs via Docker; examples use docker.all-hands.dev registry and example docker run). [1]
  • GitHub Packages / Container (GHCR) listing for the openhands container — shows available tags and docker pull syntax (e.g. docker pull ghcr.io/all-hands-ai/openhands:). [2]
  • Build/CI docs that describe image build/push to GitHub Container Registry and tagging strategy. [3]

Useful commands

  • Pull from GHCR: docker pull ghcr.io/all-hands-ai/openhands:. [2]
  • Repo example (uses docker.all-hands.dev registry): docker pull docker.all-hands.dev/all-hands-ai/openhands: && docker run ... . [1]

Sources
[1] All-Hands-AI/OpenHands README (Docker usage).
[2] GitHub Packages — All-Hands-AI / openhands container page (ghcr.io listing).
[3] OpenHands build/CI and Docker image build documentation (GHCR tagging & workflows).


🏁 Script executed:

# Look for any existing documentation about this workflow in the repository
find .github -name "*.md" -o -name "*.txt" 2>/dev/null

Repository: onlook-dev/onlook

Length of output: 210


🏁 Script executed:

# Search for the openhands-repair workflow to understand context
cat .github/workflows/openhands-repair.yml

Repository: onlook-dev/onlook

Length of output: 5187


🌐 Web query:

OpenHands Docker container --task argument flag CLI

💡 Result:

Short answer: The Dockerized OpenHands headless entry uses the same "-t / --task" CLI flag (short form -t) to pass the task text to openhands.core.main. In Docker you append the python command and task at the end, e.g.:

  • Inline task:
    docker run ... docker.openhands.dev/openhands/openhands:1.0
    python -m openhands.core.main -t "write a bash script that prints hi". [1][2]

  • Task from file:
    python -m openhands.core.main -f task.txt (or use -t with --selected-repo and other env vars when needed). Common headless options: -d (workspace), -f (task file), -i (iterations), -b (budget), --no-auto-continue. See docs for full options. [1]

Sources:
[1] OpenHands — Headless mode docs.
[2] OpenHands — Docker / CLI run examples.


🏁 Script executed:

# Check if there are any other GitHub Actions workflows that use Docker containers
# to see if there's a pattern in this repo
find .github/workflows -name "*.yml" -o -name "*.yaml" | xargs cat | grep -A 5 "docker://"

Repository: onlook-dev/onlook

Length of output: 318


Pin Docker image digest and fix args format for OpenHands CLI compatibility.

The :latest tag introduces reproducibility risks. More critically, the multiline args format breaks the --task argument—GitHub Actions splits each newline into separate arguments, fragmenting the task string and preventing proper parsing by OpenHands' CLI.

Reformat args with the task as a single quoted argument:

      - name: Run OpenHands Repair Agent
-       uses: docker://ghcr.io/all-hands-ai/openhands:latest
+       uses: docker://ghcr.io/all-hands-ai/openhands@sha256:<digest>
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
        with:
          args: |
            --task
            'Fix the CI failures in this PR.

            Context:
            - PR #${{ github.event.pull_request.number }}: ${{ github.event.pull_request.title }}
            - Branch: ${{ github.event.pull_request.head.ref }}
            - CI logs: ${{ steps.ci_logs.outputs.CI_LOGS_PATH }}

            Safety rules:
            - Only modify files in: apps/web/client/**, packages/**
            - DO NOT modify: .env*, *.key, secrets/**, billing/**
            - DO NOT create new files unless absolutely necessary
            - Commit fixes directly to this branch: ${{ github.event.pull_request.head.ref }}
            - Use commit message: '"'"'fix(openhands): Resolve CI failures [automated]'"'"'

            Steps:
            1. Read the CI failure logs
            2. Identify the root cause
            3. Make minimal fixes to resolve the failures
            4. Run tests to verify the fix
            5. Commit the changes

            Do NOT push to main. Only commit to the current PR branch.'

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
.github/workflows/openhands-repair.yml lines 51-80: the workflow uses the docker
image with the :latest tag and supplies a multiline args block which causes
GitHub Actions to split the task into multiple arguments; replace the image tag
with a pinned digest (change ghcr.io/all-hands-ai/openhands:latest to
ghcr.io/all-hands-ai/openhands@sha256:<DIGEST>) and reformat the args so the
entire task body is passed as a single quoted argument to --task (no embedded
newlines), keeping the env vars the same and preserving the rest of the flags
and commit instructions.

Comment on lines +82 to +94
- name: Verify OpenHands Changes
id: verify
run: |
# Check if OpenHands made any commits
COMMITS_SINCE_LABEL=$(git rev-list --count HEAD ^${{ github.event.before }})
if [ "$COMMITS_SINCE_LABEL" -gt 0 ]; then
echo "has_changes=true" >> $GITHUB_OUTPUT
echo "OpenHands made $COMMITS_SINCE_LABEL commit(s)"
else
echo "has_changes=false" >> $GITHUB_OUTPUT
echo "No changes made by OpenHands"
fi
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Logic error: github.event.before is not meaningful for labeled events.

In a pull_request event with type labeled, github.event.before refers to the previous base branch SHA (often undefined or irrelevant), not the commit SHA before OpenHands made changes. This will produce incorrect commit counts.

🔎 Proposed fix: capture HEAD SHA before running OpenHands

Add a step before running OpenHands to capture the current HEAD:

+     - name: Capture HEAD before repair
+       id: pre_repair
+       run: echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
+
      - name: Run OpenHands Repair Agent
        uses: docker://ghcr.io/all-hands-ai/openhands:latest
        # ... existing config ...

      - name: Verify OpenHands Changes
        id: verify
        run: |
-         # Check if OpenHands made any commits
-         COMMITS_SINCE_LABEL=$(git rev-list --count HEAD ^${{ github.event.before }})
-
-         if [ "$COMMITS_SINCE_LABEL" -gt 0 ]; then
+         # Check if OpenHands made any commits since we captured the SHA
+         CURRENT_SHA=$(git rev-parse HEAD)
+         PRE_REPAIR_SHA="${{ steps.pre_repair.outputs.sha }}"
+         
+         if [ "$CURRENT_SHA" != "$PRE_REPAIR_SHA" ]; then
+           COMMITS_COUNT=$(git rev-list --count HEAD ^"$PRE_REPAIR_SHA")
            echo "has_changes=true" >> $GITHUB_OUTPUT
-           echo "OpenHands made $COMMITS_SINCE_LABEL commit(s)"
+           echo "OpenHands made $COMMITS_COUNT commit(s)"
          else
            echo "has_changes=false" >> $GITHUB_OUTPUT
            echo "No changes made by OpenHands"
          fi

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
.github/workflows/openhands-repair.yml around lines 82-94: the current step uses
github.event.before (invalid for pull_request:labeled) to compute commits since
OpenHands; instead capture the current repository HEAD SHA in a dedicated step
before running OpenHands and use that captured SHA when computing
COMMITS_SINCE_LABEL. Add a pre-OpenHands step that runs git rev-parse HEAD and
saves it to an output/env variable (e.g., HEAD_BEFORE), then replace
github.event.before with that variable in the commit-count logic so the rev-list
compares HEAD ^$HEAD_BEFORE; ensure the new capture step runs before any
OpenHands changes are made.

Comment on lines +96 to +106
- name: Push repairs to PR branch
if: steps.verify.outputs.has_changes == 'true'
run: |
git config user.name "Cynthia OpenHands Bot"
git config user.email "openhands@cynthia.onlook.dev"
# Push to PR branch only (never main)
BRANCH="${{ github.event.pull_request.head.ref }}"
git push origin "$BRANCH"
echo "Repairs pushed to branch: $BRANCH"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Security: Same script injection vulnerability in push step.

The branch name is again directly interpolated into the shell script.

🔎 Proposed fix
      - name: Push repairs to PR branch
        if: steps.verify.outputs.has_changes == 'true'
+       env:
+         BRANCH: ${{ github.event.pull_request.head.ref }}
        run: |
          git config user.name "Cynthia OpenHands Bot"
          git config user.email "openhands@cynthia.onlook.dev"

          # Push to PR branch only (never main)
-         BRANCH="${{ github.event.pull_request.head.ref }}"
          git push origin "$BRANCH"

          echo "Repairs pushed to branch: $BRANCH"
🧰 Tools
🪛 actionlint (1.7.9)

98-98: "github.event.pull_request.head.ref" is potentially untrusted. avoid using it directly in inline scripts. instead, pass it through an environment variable. see https://docs.github.com/en/actions/reference/security/secure-use#good-practices-for-mitigating-script-injection-attacks for more details

(expression)

🤖 Prompt for AI Agents
.github/workflows/openhands-repair.yml around lines 96 to 106: the PR branch
name is interpolated directly into the shell script, opening a script injection
risk; validate the branch name against a safe git ref pattern (e.g. allow only
[A-Za-z0-9._/-] and reject if it contains spaces or shell metacharacters) and
exit on invalid input, then push using a safe git invocation that supplies the
branch as a single argument (use the POSIX-style git push with a separator and
ensure the branch variable is quoted) so untrusted characters cannot be
executed.

Comment on lines +101 to +117
// Check credits
const creditCheck = await checkCredits(userId);
if (!creditCheck.hasCredits) {
throw new TRPCError({
code: 'PAYMENT_REQUIRED',
message: `No credits remaining. You've used ${creditCheck.used}/${creditCheck.total} credits.`,
});
}

// Consume 1 credit
const consumed = await consumeCredits(userId, 1);
if (!consumed) {
throw new TRPCError({
code: 'PAYMENT_REQUIRED',
message: 'Insufficient credits',
});
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential race condition between credit check and consumption.

There's a TOCTOU (time-of-check to time-of-use) gap between checkCredits and consumeCredits. Another concurrent request could consume the last credit between these calls, causing consumeCredits to fail.

The current implementation handles this correctly by checking the return value of consumeCredits, but consider consolidating into a single atomic operation if the credit service supports it.

Comment on lines +151 to +158
async function applyPatches(
octokit: Octokit,
owner: string,
repo: string,
branch: string,
fixPackData: any,
state: ApplyState
): Promise<string[]> {
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Replace any type with proper typing for fixPackData.

The fixPackData parameter uses any, which violates the coding guideline to avoid any unless necessary. Define a proper interface for the fix pack data structure.

🔎 Recommended approach
+interface PatchDiff {
+    file: string;
+    before: string;
+    after: string;
+    description?: string;
+}
+
+interface FixPackData {
+    type: string;
+    title?: string;
+    description?: string;
+    patchPreview?: {
+        diffs?: PatchDiff[];
+    };
+    filesAffected?: string[];
+    issuesFixed?: Array<{ title?: string } | string>;
+}

 async function applyPatches(
     octokit: Octokit,
     owner: string,
     repo: string,
     branch: string,
-    fixPackData: any,
+    fixPackData: FixPackData,
     state: ApplyState
 ): Promise<string[]> {

Apply similar typing to createPullRequest at line 223.

Based on coding guidelines requiring avoidance of any types.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async function applyPatches(
octokit: Octokit,
owner: string,
repo: string,
branch: string,
fixPackData: any,
state: ApplyState
): Promise<string[]> {
interface PatchDiff {
file: string;
before: string;
after: string;
description?: string;
}
interface FixPackData {
type: string;
title?: string;
description?: string;
patchPreview?: {
diffs?: PatchDiff[];
};
filesAffected?: string[];
issuesFixed?: Array<{ title?: string } | string>;
}
async function applyPatches(
octokit: Octokit,
owner: string,
repo: string,
branch: string,
fixPackData: FixPackData,
state: ApplyState
): Promise<string[]> {
🤖 Prompt for AI Agents
In apps/web/client/src/server/api/services/apply-pipeline-service.ts around
lines 151 to 158, the parameter fixPackData is typed as any; define a proper
interface (e.g., FixPackData) that models the actual properties used by
applyPatches (fields like files[], patchText, metadata, etc.), replace the any
in the function signature with that interface, update all usages within
applyPatches to use the new typed properties, and mirror the same typed
interface for createPullRequest at line ~223 so both functions share the same
FixPackData type.

Comment on lines +346 to +351
// Mark fix pack as applied
await db
.update(fixPacks)
.set({ appliedAt: new Date() })
.where(eq(fixPacks.id, data.fixPackId));

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Check the actual column definition in the fix-pack schema
rg -A 5 -B 5 "applied" packages/db/src/schema/cynthia/fix-pack.ts

Repository: onlook-dev/onlook

Length of output: 521


🏁 Script executed:

# Check the actual code at lines 346-351 in the apply-pipeline-service.ts
sed -n '340,360p' apps/web/client/src/server/api/services/apply-pipeline-service.ts

Repository: onlook-dev/onlook

Length of output: 757


Fix column name: appliedAt does not exist in the schema.

The code uses appliedAt, but the fixPacks schema defines the field as applied (database column: applied_at). This will cause a runtime error.

         // Mark fix pack as applied
         await db
             .update(fixPacks)
-            .set({ appliedAt: new Date() })
+            .set({ applied: new Date() })
             .where(eq(fixPacks.id, data.fixPackId));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Mark fix pack as applied
await db
.update(fixPacks)
.set({ appliedAt: new Date() })
.where(eq(fixPacks.id, data.fixPackId));
// Mark fix pack as applied
await db
.update(fixPacks)
.set({ applied: new Date() })
.where(eq(fixPacks.id, data.fixPackId));
🤖 Prompt for AI Agents
In apps/web/client/src/server/api/services/apply-pipeline-service.ts around
lines 346 to 351, the code sets a non-existent field appliedAt on fixPacks; the
schema uses applied (DB column applied_at). Change the update to set the correct
field name ({ applied: new Date() }) so the ORM/DB mapping matches the schema
and avoid the runtime error.

Comment on lines +10 to +18
const redisConnection = {
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT || '6379', 10),
maxRetriesPerRequest: null,
enableReadyCheck: false,
};

// Create Redis client for queues
const redisClient = new Redis(redisConnection);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Redis client created at module load time without error handling.

The Redis client is instantiated when the module loads. If Redis is unavailable, this will throw and crash the application during startup. Consider lazy initialization or adding connection error handling.

🔎 Recommended approach
-// Create Redis client for queues
-const redisClient = new Redis(redisConnection);
+// Create Redis client for queues (lazy initialization for health checks)
+let redisClient: Redis | null = null;
+
+function getRedisClient(): Redis {
+    if (!redisClient) {
+        redisClient = new Redis(redisConnection);
+        redisClient.on('error', (err) => {
+            console.error('[Queue Service] Redis connection error:', err);
+        });
+    }
+    return redisClient;
+}

Then update checkQueueHealth and closeQueues to use getRedisClient().

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/web/client/src/server/api/services/queue-service.ts around lines 10 to
18, the Redis client is created at module load time without error handling which
can crash startup if Redis is unavailable; change to a lazy initializer
getRedisClient() that creates the Redis instance on first call, wrap the
creation/connection in a try/catch to log errors and avoid uncaught exceptions
(returning or rethrowing a meaningful error), attach appropriate connection
event handlers for retries/errors, and update checkQueueHealth and closeQueues
to call getRedisClient() (and handle a missing/null client) instead of using a
module-level redisClient.

Comment on lines +21 to +24
// Validate job data
if (!applyRunId || !userId || !fixPackId || !repoOwner || !repoName) {
throw new Error('Missing required job data');
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing auditId validation.

The validation check omits auditId, but it's destructured (line 19) and passed to processApplyFixPack (line 33). Per the ApplyFixPackJobData interface, auditId is a required field.

🔎 Proposed fix
     // Validate job data
-    if (!applyRunId || !userId || !fixPackId || !repoOwner || !repoName) {
+    if (!applyRunId || !userId || !auditId || !fixPackId || !repoOwner || !repoName) {
         throw new Error('Missing required job data');
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Validate job data
if (!applyRunId || !userId || !fixPackId || !repoOwner || !repoName) {
throw new Error('Missing required job data');
}
// Validate job data
if (!applyRunId || !userId || !auditId || !fixPackId || !repoOwner || !repoName) {
throw new Error('Missing required job data');
}
🤖 Prompt for AI Agents
In apps/web/client/src/server/api/workers/apply-worker.ts around lines 21 to 24,
the current validation omits auditId even though it is destructured and required
by ApplyFixPackJobData; update the validation to include auditId in the
required-fields check (e.g., ensure auditId is truthy in the same conditional)
and update the thrown error message if desired to reflect which field(s) are
missing so processApplyFixPack always receives a valid auditId.

Dependencies (No Manual Steps Required):
- Add bullmq ^5.28.2 to package.json
- Add ioredis ^5.4.2 to package.json
- Dependencies ready for bun install (no post-install required)

Worker Process (Dedicated, NOT in Next.js):
- Create apps/web/client/src/server/workers/start-workers.ts
- Add "workers" script to package.json
- Starts apply worker + monitor worker
- Health check on Redis connection
- Graceful shutdown on SIGINT/SIGTERM
- Usage: bun run workers (separate terminal)

CI Status Resolution (Automatic Polling MVP):
- Create ci-monitoring-service.ts with monitorChecks()
- Polls GitHub check runs every 20s (configurable)
- Max timeout: 20 minutes (60 attempts * 20s)
- Summarizes check statuses (queued/in_progress/completed)
- Transitions apply_run: checks_running → success OR failed
- No manual intervention required

Repair Trigger (Automatic Label Application):
- monitorChecks() calls triggerRepair() on CI failure
- Applies agent:repair label to PR via GitHub API
- Guard: checks if label already exists (no duplicates)
- Triggers OpenHands workflow automatically
- One repair attempt per apply run (MVP scope)

Monitor Queue + Worker:
- Add monitorQueue to queue-service.ts (cynthia-monitor)
- Concurrency: 3 (can monitor 3 PRs simultaneously)
- Lock duration: 20 minutes
- Create monitor-worker.ts with processMonitorJob()
- Wire into start-workers.ts with apply worker

Pipeline Integration:
- apply-pipeline-service.ts now calls queueMonitorChecks()
- After PR creation, queues monitoring job
- Monitoring starts automatically when PR opens
- Status transitions happen without manual action

Acceptance Criteria Met:
✅ Dependencies in package.json (bun install works)
✅ Dedicated worker process (bun run workers)
✅ CI status auto-resolves (polling MVP)
✅ Repair trigger automatic (label applied on failure)

Ready for Phase 5.1 verification and approval.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (4)
apps/web/client/src/server/api/services/queue-service.ts (1)

10-18: Redis client created at module load time without error handling.

This was flagged in a previous review. Consider lazy initialization or adding connection error handling to prevent crashes if Redis is unavailable at startup.

apps/web/client/src/server/api/services/apply-pipeline-service.ts (3)

152-159: Replace any type with proper typing for fixPackData.

This was flagged in a previous review. The any type violates coding guidelines.


219-226: Same any type issue as applyPatches.

The fixPackData: any parameter should use the same typed interface.


347-351: Fix column name: appliedAt does not exist in the schema.

This was flagged in a previous review. The schema defines the field as applied (database column: applied_at), not appliedAt.

🔎 Required fix
         // Mark fix pack as applied
         await db
             .update(fixPacks)
-            .set({ appliedAt: new Date() })
+            .set({ applied: new Date() })
             .where(eq(fixPacks.id, data.fixPackId));
🧹 Nitpick comments (4)
apps/web/client/src/server/workers/start-workers.ts (1)

52-73: Consider extracting shutdown logic to reduce duplication.

The SIGINT and SIGTERM handlers contain identical logic. This is functionally correct, but could be DRYed up.

🔎 Optional refactor to reduce duplication
+    async function gracefulShutdown(signal: string) {
+        console.log('');
+        console.log(`🛑 [Workers] Received ${signal}, shutting down gracefully...`);
+
+        await applyWorker.close();
+        await monitorWorker.close();
+
+        console.log('✅ [Workers] Workers stopped');
+        process.exit(0);
+    }
+
     // Handle graceful shutdown
-    process.on('SIGINT', async () => {
-        console.log('');
-        console.log('🛑 [Workers] Received SIGINT, shutting down gracefully...');
-
-        await applyWorker.close();
-        await monitorWorker.close();
-
-        console.log('✅ [Workers] Workers stopped');
-        process.exit(0);
-    });
-
-    process.on('SIGTERM', async () => {
-        console.log('');
-        console.log('🛑 [Workers] Received SIGTERM, shutting down gracefully...');
-
-        await applyWorker.close();
-        await monitorWorker.close();
-
-        console.log('✅ [Workers] Workers stopped');
-        process.exit(0);
-    });
+    process.on('SIGINT', () => gracefulShutdown('SIGINT'));
+    process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
apps/web/client/src/server/api/services/ci-monitoring-service.ts (2)

96-124: Consider the state transition ordering when repair is triggered.

When CI fails, the status is set to 'failed' before triggering repair. Users may briefly see a "failed" status even though repair is in progress. Consider whether a distinct status like 'repair_triggered' would provide better UX.


179-217: Replace any[] with proper typing for checkRuns.

The checkRuns: any[] parameter type violates the coding guideline to avoid any unless necessary. The GitHub API returns a well-defined structure.

🔎 Recommended approach
+interface CheckRun {
+    status: string;
+    conclusion: string | null;
+    name: string;
+}
+
-function summarizeCheckRuns(checkRuns: any[]): CheckRunSummary {
+function summarizeCheckRuns(checkRuns: CheckRun[]): CheckRunSummary {

Alternatively, you can import the type from Octokit:

import type { RestEndpointMethodTypes } from '@octokit/rest';
type CheckRun = RestEndpointMethodTypes['checks']['listForRef']['response']['data']['check_runs'][number];

Based on coding guidelines requiring avoidance of any types.

apps/web/client/src/server/api/services/apply-pipeline-service.ts (1)

375-380: Duplicate status update to checks_running.

The status is already updated to checks_running at line 356-360. This second update at lines 376-380 is redundant.

🔎 Remove duplicate status update
         addLog(state, 'info', 'CI monitoring queued - status will auto-update');
-        await updateApplyRunStatus(state, 'checks_running', {
-            branch: branchName,
-            prNumber: pr.number,
-            prUrl: pr.url,
-        });
     } catch (error: any) {
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cfb0f58 and 137d534.

📒 Files selected for processing (6)
  • apps/web/client/package.json
  • apps/web/client/src/server/api/services/apply-pipeline-service.ts
  • apps/web/client/src/server/api/services/ci-monitoring-service.ts
  • apps/web/client/src/server/api/services/queue-service.ts
  • apps/web/client/src/server/api/workers/monitor-worker.ts
  • apps/web/client/src/server/workers/start-workers.ts
🧰 Additional context used
📓 Path-based instructions (3)
apps/web/client/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/**/*.{ts,tsx}: Use path aliases @/* and ~/* for imports that map to apps/web/client/src/*
Avoid hardcoded user-facing text; use next-intl messages/hooks instead

Use path aliases @/* and ~/* for imports mapping to src/*

Files:

  • apps/web/client/src/server/workers/start-workers.ts
  • apps/web/client/src/server/api/services/ci-monitoring-service.ts
  • apps/web/client/src/server/api/workers/monitor-worker.ts
  • apps/web/client/src/server/api/services/queue-service.ts
  • apps/web/client/src/server/api/services/apply-pipeline-service.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Do not use the any type unless necessary

Files:

  • apps/web/client/src/server/workers/start-workers.ts
  • apps/web/client/src/server/api/services/ci-monitoring-service.ts
  • apps/web/client/src/server/api/workers/monitor-worker.ts
  • apps/web/client/src/server/api/services/queue-service.ts
  • apps/web/client/src/server/api/services/apply-pipeline-service.ts
{apps,packages}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Avoid using the any type unless absolutely necessary

Files:

  • apps/web/client/src/server/workers/start-workers.ts
  • apps/web/client/src/server/api/services/ci-monitoring-service.ts
  • apps/web/client/src/server/api/workers/monitor-worker.ts
  • apps/web/client/src/server/api/services/queue-service.ts
  • apps/web/client/src/server/api/services/apply-pipeline-service.ts
🧠 Learnings (3)
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Use Bun for all installs and scripts; do not introduce npm, yarn, or pnpm

Applied to files:

  • apps/web/client/package.json
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to {apps,packages}/**/*.{ts,tsx} : Avoid using the any type unless absolutely necessary

Applied to files:

  • apps/web/client/src/server/api/services/apply-pipeline-service.ts
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
Repo: onlook-dev/onlook PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to **/*.{ts,tsx} : Do not use the any type unless necessary

Applied to files:

  • apps/web/client/src/server/api/services/apply-pipeline-service.ts
🧬 Code graph analysis (3)
apps/web/client/src/server/workers/start-workers.ts (3)
apps/web/client/src/server/api/services/queue-service.ts (1)
  • checkQueueHealth (276-284)
apps/web/client/src/server/api/workers/apply-worker.ts (1)
  • initializeApplyWorker (48-53)
apps/web/client/src/server/api/workers/monitor-worker.ts (1)
  • initializeMonitorWorker (49-54)
apps/web/client/src/server/api/workers/monitor-worker.ts (3)
apps/web/client/src/server/api/services/queue-service.ts (2)
  • MonitorChecksJobData (47-55)
  • startMonitorWorker (190-216)
packages/git/src/git.ts (1)
  • branch (116-123)
apps/web/client/src/server/api/services/ci-monitoring-service.ts (1)
  • monitorChecks (29-174)
apps/web/client/src/server/api/services/apply-pipeline-service.ts (5)
packages/git/src/git.ts (2)
  • status (89-91)
  • branch (116-123)
packages/db/src/client.ts (1)
  • db (16-16)
packages/db/src/schema/cynthia/fix-pack.ts (1)
  • fixPacks (18-49)
packages/github/src/auth.ts (1)
  • createInstallationOctokit (8-22)
apps/web/client/src/server/api/services/queue-service.ts (1)
  • queueMonitorChecks (250-258)
🪛 Biome (2.1.2)
apps/web/client/src/server/api/services/apply-pipeline-service.ts

[error] 305-305: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 309-309: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 364-364: Shouldn't redeclare 'installationId'. Consider to delete it or rename it.

'installationId' is defined here:

(lint/suspicious/noRedeclare)

🔇 Additional comments (23)
apps/web/client/package.json (1)

32-33: LGTM! Worker script and dependencies properly configured.

The new workers script correctly uses Bun as per project conventions. The bullmq and ioredis dependencies are appropriate for the BullMQ-based worker infrastructure.

Also applies to: 77-77, 85-85

apps/web/client/src/server/workers/start-workers.ts (3)

1-11: LGTM! Clean module setup with appropriate imports.

The imports correctly reference the worker initializers and health check from the queue service using proper path aliases.


16-28: LGTM! Good fail-fast health check with helpful error messaging.

The health check correctly validates Redis connectivity before worker startup, and the error message with the Docker command is operationally helpful.


76-80: LGTM! Proper top-level error handling.

The entrypoint correctly catches fatal errors and exits with a non-zero code.

apps/web/client/src/server/api/workers/monitor-worker.ts (3)

1-11: LGTM! Clean imports with proper type-only import for Job.

The imports correctly use type for the Job interface and import from the appropriate service modules.


16-43: LGTM! Proper job data validation and processing.

The function correctly validates all required fields before processing and delegates to monitorChecks for the actual CI monitoring logic.


49-54: LGTM! Clean worker initialization.

The initialization function follows the same pattern as the apply worker and correctly returns the worker instance for lifecycle management.

apps/web/client/src/server/api/services/ci-monitoring-service.ts (5)

1-24: LGTM! Well-structured types and imports.

The type definitions for CheckStatus, CheckConclusion, and CheckRunSummary provide good type safety for the monitoring logic.


29-49: LGTM! Well-designed polling configuration.

The default values (60 attempts × 20s = 20 minutes) provide reasonable coverage for most CI pipelines, and the configurable parameters allow flexibility.


222-258: LGTM! Idempotent repair trigger with proper error handling.

The function correctly checks for existing labels before adding, and the intentional error suppression is appropriate since label failure shouldn't block monitoring.


263-282: LGTM! Proper use of parameterized SQL for stored procedure call.

The raw SQL with Drizzle's sql template literal provides safe parameter interpolation when calling the database function.


287-289: LGTM! Simple sleep utility.

apps/web/client/src/server/api/services/apply-pipeline-service.ts (6)

1-41: LGTM! Well-structured logging and state management.

The LogEntry interface and addLog helper provide consistent logging across the pipeline with timestamps and levels.


46-68: LGTM! Clean database status update function.

The updateApplyRunStatus function correctly uses parameterized SQL to call the stored procedure.


70-114: LGTM! Branch creation logic is correct.

The function properly retrieves the default branch SHA and creates a new branch reference.


116-147: LGTM! Robust patch application with safety checks.

The safety checks for substring existence and uniqueness provide good protection against accidental corruption or ambiguous patches.


166-214: LGTM! Patch iteration and commit logic is sound.

The file content retrieval, patch application, and commit workflow is well-structured with proper error handling per file.


228-263: LGTM! PR creation with informative body.

The PR body includes relevant information about files changed and issues fixed, and the auto-generated footer is helpful.

apps/web/client/src/server/api/services/queue-service.ts (5)

20-55: LGTM! Well-defined job data interfaces.

The interfaces provide clear contracts for each job type with all necessary fields properly typed.


57-114: LGTM! Queue configurations with sensible defaults.

The retry policies, backoff strategies, and job retention settings are well-thought-out for each queue's use case:

  • Apply queue: 3 retries with exponential backoff for transient failures
  • Repair queue: 2 retries for repair attempts
  • Monitor queue: No retries since monitoring has internal retry logic

116-216: LGTM! Singleton worker pattern with proper lifecycle hooks.

The worker starter functions correctly implement singleton pattern to prevent duplicate workers, and the event handlers provide visibility into job completion/failure.


218-258: LGTM! Idempotent job queuing with clear deduplication keys.

The job ID patterns (apply-{id}, repair-{id}, monitor-{id}) ensure idempotency and traceability.


260-284: LGTM! Clean shutdown and health check implementation.

The closeQueues function properly closes workers, queues, and the Redis client. The health check using PING/PONG is a standard Redis connectivity test.

Comment on lines +362 to +373
// Phase 5.1: Queue CI monitoring job to auto-update status
addLog(state, 'info', 'Queueing CI monitoring job...');
const installationId = data.githubInstallationId || process.env.GITHUB_INSTALLATION_ID;
await queueMonitorChecks({
applyRunId: data.applyRunId,
userId: data.userId,
repoOwner: data.repoOwner,
repoName: data.repoName,
prNumber: pr.number,
branch: branchName,
githubInstallationId: installationId,
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: installationId is redeclared in the same scope.

Static analysis correctly identified this issue. The variable installationId is already declared at line 304 and redeclared at line 364, which will cause a runtime error.

🔎 Required fix - remove the redeclaration
         // Phase 5.1: Queue CI monitoring job to auto-update status
         addLog(state, 'info', 'Queueing CI monitoring job...');
-        const installationId = data.githubInstallationId || process.env.GITHUB_INSTALLATION_ID;
         await queueMonitorChecks({
             applyRunId: data.applyRunId,
             userId: data.userId,
             repoOwner: data.repoOwner,
             repoName: data.repoName,
             prNumber: pr.number,
             branch: branchName,
             githubInstallationId: installationId,
         });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Phase 5.1: Queue CI monitoring job to auto-update status
addLog(state, 'info', 'Queueing CI monitoring job...');
const installationId = data.githubInstallationId || process.env.GITHUB_INSTALLATION_ID;
await queueMonitorChecks({
applyRunId: data.applyRunId,
userId: data.userId,
repoOwner: data.repoOwner,
repoName: data.repoName,
prNumber: pr.number,
branch: branchName,
githubInstallationId: installationId,
});
// Phase 5.1: Queue CI monitoring job to auto-update status
addLog(state, 'info', 'Queueing CI monitoring job...');
await queueMonitorChecks({
applyRunId: data.applyRunId,
userId: data.userId,
repoOwner: data.repoOwner,
repoName: data.repoName,
prNumber: pr.number,
branch: branchName,
githubInstallationId: installationId,
});
🧰 Tools
🪛 Biome (2.1.2)

[error] 364-364: Shouldn't redeclare 'installationId'. Consider to delete it or rename it.

'installationId' is defined here:

(lint/suspicious/noRedeclare)

🤖 Prompt for AI Agents
In apps/web/client/src/server/api/services/apply-pipeline-service.ts around
lines 362 to 373 (previous declaration at ~line 304), you redeclare
installationId causing a duplicate-declaration/runtime error; remove the "const"
redeclaration and instead reuse the already-declared installationId (assign to
it or compute a fallback value without reintroducing the variable), then pass
that existing variable into queueMonitorChecks.

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.

2 participants