Skip to content

Conversation

@vibegui
Copy link
Contributor

@vibegui vibegui commented Oct 27, 2025

split from #1641

  • Add BROWSER_SCREENSHOT, BROWSER_PDF, BROWSER_HTML, BROWSER_SCRAPE tools
  • Create browser-rendering UI with screenshot gallery, date filters, and quick capture
  • Add SDK hooks (useCaptureScreenshot, useScreenshots, useDeleteScreenshot, etc.)
  • Integrate with chat: auto-refresh gallery on screenshot capture
  • Add browser-rendering routes to org and project layouts
  • Register Browser as native app in marketplace
  • Add migration for browser-rendering and filesystem policies
  • Update .dev.vars.example with CF_API_BROWSER_RENDERING token placeholder

Screenshots are automatically saved to R2 in browser-rendering/screenshots/ organized by date. AI chat can capture screenshots with inline preview and gallery auto-refresh.

What is this contribution about?

Describe your changes and why they're needed.

Screenshots/Demonstration

Add screenshots or a Loom video if your changes affect the UI.

Review Checklist

  • PR title is clear and descriptive
  • Changes are tested and working
  • Documentation is updated (if needed)
  • No breaking changes

Summary by CodeRabbit

  • New Features
    • Added Browser feature enabling screenshot capture, PDF generation, HTML fetching, and website scraping.
    • Added screenshot gallery with search filtering, date-range filtering, and management capabilities (view, delete).
    • Integrated browser rendering tools into AI assistant context for enhanced automation.

- Add BROWSER_SCREENSHOT, BROWSER_PDF, BROWSER_HTML, BROWSER_SCRAPE tools
- Create browser-rendering UI with screenshot gallery, date filters, and quick capture
- Add SDK hooks (useCaptureScreenshot, useScreenshots, useDeleteScreenshot, etc.)
- Integrate with chat: auto-refresh gallery on screenshot capture
- Add browser-rendering routes to org and project layouts
- Register Browser as native app in marketplace
- Add migration for browser-rendering and filesystem policies
- Update .dev.vars.example with CF_API_BROWSER_RENDERING token placeholder

Screenshots are automatically saved to R2 in browser-rendering/screenshots/ organized by date.
AI chat can capture screenshots with inline preview and gallery auto-refresh.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 27, 2025

Walkthrough

This PR introduces comprehensive browser rendering functionality including screenshot capture, PDF generation, HTML fetching, and website scraping. It adds web UI components for screenshot gallery management, SDK CRUD operations and React Query hooks, MCP tools interfacing with Cloudflare Browser Rendering API, database access control policies, navigation integration, and environment configuration.

Changes

Cohort / File(s) Summary
Environment Configuration
apps/api/.dev.vars.example
Adds optional CF_API_BROWSER_RENDERING environment variable for Cloudflare Browser Rendering API token.
Web Components – Browser Rendering
apps/web/src/components/browser-rendering/browser-rendering-resource-list.tsx, apps/web/src/components/browser-rendering/browser-rendering-view.tsx
New components: ResourceList wires context and tracks view visits; View implements screenshot capture form, quick filters (search, date-range), gallery with responsive grid, lightbox dialog, and optimistic UI feedback with mutations and error handling.
Web Integration – Chat & Sidebar
apps/web/src/components/chat/provider.tsx, apps/web/src/components/sidebar/index.tsx, apps/web/src/components/integrations/apps.ts
Invalidates screenshot gallery queries after BROWSER_SCREENSHOT tool calls; adds Browser item to sidebar with camera icon and explicit route; registers native browser-rendering app with metadata.
Web Routing
apps/web/src/main.tsx
Adds lazy-loaded routes for /browser-rendering path across organization and project-level sections.
SDK Exports
packages/sdk/package.json
Exports new ./hooks/browser-rendering module for public consumption.
SDK CRUD Operations
packages/sdk/src/crud/browser-rendering.ts
Defines interfaces and functions for browser rendering: captureScreenshot, generatePdf, fetchHtml, scrapeWebsite, listScreenshots, deleteScreenshot with locator-based MCPClient delegation.
SDK React Query Hooks
packages/sdk/src/hooks/browser-rendering.ts
Provides useScreenshots query hook and mutation hooks (useCaptureScreenshot, useGeneratePdf, useFetchHtml, useScrapeWebsite, useDeleteScreenshot) with automatic cache invalidation and locator validation.
SDK MCP Browser Rendering Tools
packages/sdk/src/mcp/browser-rendering/api.ts
Implements six tools: BROWSER_SCREENSHOT, BROWSER_PDF, BROWSER_HTML, BROWSER_SCRAPE, BROWSER_SCREENSHOTS_LIST, BROWSER_SCREENSHOT_DELETE with Cloudflare API calls, R2 storage uploads, auth headers, and error handling.
SDK MCP Tool Registry
packages/sdk/src/mcp/index.ts
Integrates six browser rendering tools into PROJECT_TOOLS registry.
Database Access Policies
supabase/migrations/20251026110000_browser_rendering_and_fs_policies.sql
Creates four policies (browser rendering usage, screenshot management, file system usage, file deletion) and assigns them to owner, member, and admin roles with conflict handling.

Sequence Diagram

sequenceDiagram
    actor User
    participant WebApp as Web App<br/>(BrowserRenderingView)
    participant ChatProvider as Chat Provider<br/>(Tool Interceptor)
    participant MCP as MCP Layer<br/>(Browser Tools)
    participant CloudflareAPI as Cloudflare API<br/>(Browser Rendering)
    participant R2 as Cloudflare R2<br/>(Storage)
    participant QueryCache as React Query<br/>(Cache)

    User->>WebApp: Submit URL for screenshot
    WebApp->>MCP: useCaptureScreenshot mutation
    MCP->>CloudflareAPI: POST /render (url)
    CloudflareAPI-->>R2: Upload screenshot image
    R2-->>MCP: Public URL
    MCP-->>WebApp: Success + screenshot metadata
    
    ChatProvider->>ChatProvider: Detect BROWSER_SCREENSHOT tool call
    ChatProvider->>QueryCache: Invalidate screenshots
    QueryCache->>WebApp: Refetch listScreenshots
    
    rect rgba(200, 150, 255, 0.2)
        Note over WebApp: Gallery auto-updates<br/>with new screenshot
    end
    
    WebApp-->>User: Display new screenshot in gallery
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • packages/sdk/src/mcp/browser-rendering/api.ts — Substantial logic density with Cloudflare API integration, R2 storage interactions, auth validation, error handling paths, and multiple tool implementations that require verification of correct error messages, credential handling, and response parsing.
  • apps/web/src/components/browser-rendering/browser-rendering-view.tsx — Dense component with multiple hooks, mutations, state management, filtering logic, gallery rendering, dialog handling, and async data flows that need careful validation of UX state transitions and error scenarios.
  • packages/sdk/src/hooks/browser-rendering.ts & packages/sdk/src/crud/browser-rendering.ts — Verify consistency between hooks and CRUD layer, confirm proper locator extraction and validation, ensure cache invalidation strategies align with UI expectations.
  • Database migration — Verify policy IDs don't conflict, role associations are correct, and ON CONFLICT handling is appropriate.

Poem

🐰 Hop and screenshot, capture the web,
PDFs and HTML, every breadcrumb tread,
Gallery glimmers with images new,
Cloudflare and R2, rendering true! 📸✨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The PR description includes substantive information in a bullet-point summary at the top that lists the key additions (tools, UI, SDK hooks, chat integration, routes, native app registration, migration, and configuration updates) along with storage behavior details. However, the description template sections are not properly completed: the "What is this contribution about?" section remains unfilled with only the template placeholder text, the "Screenshots/Demonstration" section is empty despite significant UI changes to the browser-rendering component, and the Review Checklist items are all unchecked. While the developer did provide useful information, the required template structure is not followed, and important UI documentation is missing for a change that introduces a new gallery interface with filtering capabilities. Fill out the "What is this contribution about?" section with a clear explanation of the changes and their purpose. Add screenshots or a demonstration video showing the browser-rendering UI components, particularly the screenshot gallery interface and capture functionality. Complete the Review Checklist by marking items that have been addressed. This will ensure the description follows the repository template and provides essential context for reviewers.
Docstring Coverage ⚠️ Warning Docstring coverage is 21.43% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The PR title "feat(browser-rendering): add Cloudflare Browser Rendering integration" clearly and directly summarizes the main contribution, which is the addition of Cloudflare Browser Rendering functionality to the platform. The title uses the conventional commit format (feat), is concise and specific, and accurately reflects the core objective of the changeset. While the PR includes numerous supporting changes (UI components, SDK hooks, routing, native app registration, and database migrations), the title appropriately focuses on the primary feature being introduced without claiming to capture every detail.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch gui/browser-rendering

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

@cloudflare-workers-and-pages
Copy link

Deploying decocms-admin-frontend with  Cloudflare Pages  Cloudflare Pages

Latest commit: 93da402
Status: ✅  Deploy successful!
Preview URL: https://0c75a753.admin-decocms-frontend.pages.dev
Branch Preview URL: https://gui-browser-rendering.admin-decocms-frontend.pages.dev

View logs

Copy link
Contributor

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

🧹 Nitpick comments (6)
apps/web/src/components/browser-rendering/browser-rendering-view.tsx (2)

301-370: Sort screenshots newest-first for better UX

Currently order is storage enumeration dependent. Sort by lastModified desc before rendering.

-  const filteredScreenshots = useMemo(() => { ... }, [ ... ]);
+  const filteredScreenshots = useMemo(() => { ... }, [ ... ]);
+  const sortedScreenshots = useMemo(
+    () =>
+      [...filteredScreenshots].sort(
+        (a, b) =>
+          new Date(b.lastModified).getTime() - new Date(a.lastModified).getTime(),
+      ),
+    [filteredScreenshots],
+  );
...
-            {filteredScreenshots.map((screenshot) => (
+            {sortedScreenshots.map((screenshot) => (

[ suggest_optional_refactor ]


135-146: Prefer a design-system confirm over native confirm()

Use an AlertDialog from @deco/ui for consistency and accessibility.

packages/sdk/src/hooks/browser-rendering.ts (1)

30-53: Adopt suspense and (optionally) optimistic delete

  • Switch to useSuspenseQuery for critical lists to align with the guideline.
  • For delete, consider onMutate/onError to optimistically remove the item and rollback on failure.

As per coding guidelines

Also applies to: 106-121

packages/sdk/src/mcp/browser-rendering/api.ts (2)

181-195: Add AbortController timeouts to all Cloudflare calls

Only SCREENSHOT uses a 30s timeout. Mirror this in PDF, HTML, and SCRAPE to avoid hung handlers.

+    const controller = new AbortController();
+    const timeoutId = setTimeout(() => controller.abort(), 30000);
     const response = await fetch(..., {
       method: "POST",
       headers: buildCloudflareAuthHeaders(env),
       body: JSON.stringify(requestBody),
+      signal: controller.signal,
     });
+    clearTimeout(timeoutId);

Also applies to: 376-389, 545-552, 488-495


175-180: Reduce noisy logs or guard behind debug flag

Console logs include payload details and may clutter logs. Prefer structured debug logs behind a verbosity flag.

Also applies to: 620-641, 653-666

packages/sdk/src/crud/browser-rendering.ts (1)

61-71: CRUD surface looks consistent and typed correctly

Interfaces and MCP calls align. Consider consistently unpacking or returning full objects across functions for API symmetry, but it’s optional.

Also applies to: 96-103, 122-128, 148-154, 162-172, 179-189

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d6575fc and 93da402.

📒 Files selected for processing (13)
  • apps/api/.dev.vars.example (1 hunks)
  • apps/web/src/components/browser-rendering/browser-rendering-resource-list.tsx (1 hunks)
  • apps/web/src/components/browser-rendering/browser-rendering-view.tsx (1 hunks)
  • apps/web/src/components/chat/provider.tsx (1 hunks)
  • apps/web/src/components/integrations/apps.ts (2 hunks)
  • apps/web/src/components/sidebar/index.tsx (3 hunks)
  • apps/web/src/main.tsx (2 hunks)
  • packages/sdk/package.json (1 hunks)
  • packages/sdk/src/crud/browser-rendering.ts (1 hunks)
  • packages/sdk/src/hooks/browser-rendering.ts (1 hunks)
  • packages/sdk/src/mcp/browser-rendering/api.ts (1 hunks)
  • packages/sdk/src/mcp/index.ts (2 hunks)
  • supabase/migrations/20251026110000_browser_rendering_and_fs_policies.sql (1 hunks)
🧰 Additional context used
📓 Path-based instructions (19)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/data-flow.mdc)

**/*.{ts,tsx}: MCP tools must use Zod schemas for input and, when applicable, output validation
Register tools with server.registerTool providing description, inputSchema.shape, and optional outputSchema.shape
In every MCP tool handler, perform authorization checks first and then call context.resourceAccess.grant() before business logic
Name tools using the {RESOURCE}_{ACTION} pattern (e.g., AGENTS_CREATE, THREADS_LIST)
Group related tools into typed collections (e.g., GLOBAL_TOOLS, WORKSPACE_TOOLS) and export them as const
Always check workspace/team access before executing operations that touch workspace resources
Return proper authorization errors and handle forbidden operations explicitly

Prefer specific TypeScript types over any

**/*.{ts,tsx}: Write concise, maintainable, and technically accurate TypeScript code
Use functional and declarative programming patterns; avoid classes
Favor iteration and modularization to follow DRY and avoid code duplication
Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError)
Organize files so each contains only related content (components, subcomponents, helpers, static content, types)
Prefer interfaces over types for object shapes
Avoid enums; use maps (objects/records) instead
Use the function keyword for pure functions to benefit from hoisting and clarity
Ensure dependency arrays use stable references; do not inline new objects/arrays/functions
Memoize options/objects used in hooks (e.g., useMemo for options passed to useMemo/useCallback)
Favor named exports for functions

Files:

  • apps/web/src/components/browser-rendering/browser-rendering-resource-list.tsx
  • apps/web/src/components/chat/provider.tsx
  • packages/sdk/src/hooks/browser-rendering.ts
  • packages/sdk/src/mcp/index.ts
  • apps/web/src/components/integrations/apps.ts
  • packages/sdk/src/mcp/browser-rendering/api.ts
  • apps/web/src/components/browser-rendering/browser-rendering-view.tsx
  • apps/web/src/components/sidebar/index.tsx
  • packages/sdk/src/crud/browser-rendering.ts
  • apps/web/src/main.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/data-flow.mdc)

**/*.tsx: Use the shared KEYS object to construct consistent React Query keys
Use useSuspenseQuery for critical data fetching hooks
Implement optimistic updates for mutations and provide rollback on error via onMutate/onError
After successful mutations, update relevant caches with setQueryData (e.g., entity detail and list keys)
Leverage React Query’s built-in request deduplication instead of manual throttling
Use parallel queries where appropriate to reduce total load time

**/*.tsx: Use functional React components typed with TypeScript interfaces
Prefer using existing UI components from packages/ui over custom duplicates
Always use design system components from @deco/ui in UI code instead of hand-rolled equivalents
Memoize expensive computations with useMemo (e.g., filtering/sorting)
Memoize objects/arrays used in renders to keep stable references (useMemo)
Use useDeferredValue to keep search inputs responsive during heavy filtering
Follow MCP Tool data-access patterns (e.g., useAgents, useIntegrations) with proper loading/error states
Prefer react-hook-form with schema validation (e.g., zod) over manual useState for forms
Avoid prop drilling for forms by using a form context/provider
Adhere to Single Responsibility Principle by splitting large components into focused subcomponents
Design for composability and reusability of small UI components (e.g., Avatar variants)
Provide consistent loading and error states for data-driven components (e.g., Spinner, ErrorMessage, EmptyState)
Extract complex inline logic from JSX (avoid IIFEs) into small components/functions

Files:

  • apps/web/src/components/browser-rendering/browser-rendering-resource-list.tsx
  • apps/web/src/components/chat/provider.tsx
  • apps/web/src/components/browser-rendering/browser-rendering-view.tsx
  • apps/web/src/components/sidebar/index.tsx
  • apps/web/src/main.tsx
apps/web/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (apps/web/.cursor/rules/posthog-integration.mdc)

apps/web/**/*.{js,jsx,ts,tsx}: Use each feature flag in as few places as possible; if a flag must appear at multiple callsites, explicitly flag this for careful developer review
Gate any flag-dependent code behind checks that verify the flag’s values are valid and expected
If a custom person or event property is referenced in two or more files or at two or more callsites in the same file, centralize the keys in an enum (TS) or const object (JS)

Files:

  • apps/web/src/components/browser-rendering/browser-rendering-resource-list.tsx
  • apps/web/src/components/chat/provider.tsx
  • apps/web/src/components/integrations/apps.ts
  • apps/web/src/components/browser-rendering/browser-rendering-view.tsx
  • apps/web/src/components/sidebar/index.tsx
  • apps/web/src/main.tsx
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/web/.cursor/rules/posthog-integration.mdc)

In TypeScript, store feature flag names in an enum with members written UPPERCASE_WITH_UNDERSCORE and use a consistent naming convention

Files:

  • apps/web/src/components/browser-rendering/browser-rendering-resource-list.tsx
  • apps/web/src/components/chat/provider.tsx
  • apps/web/src/components/integrations/apps.ts
  • apps/web/src/components/browser-rendering/browser-rendering-view.tsx
  • apps/web/src/components/sidebar/index.tsx
  • apps/web/src/main.tsx
apps/web/**

📄 CodeRabbit inference engine (AGENTS.md)

Place the Vite/React web client in apps/web

Files:

  • apps/web/src/components/browser-rendering/browser-rendering-resource-list.tsx
  • apps/web/src/components/chat/provider.tsx
  • apps/web/src/components/integrations/apps.ts
  • apps/web/src/components/browser-rendering/browser-rendering-view.tsx
  • apps/web/src/components/sidebar/index.tsx
  • apps/web/src/main.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Biome formatting: use two-space indentation and double quotes
Keep imports sorted
Name hooks and utility functions using camelCase

Files:

  • apps/web/src/components/browser-rendering/browser-rendering-resource-list.tsx
  • apps/web/src/components/chat/provider.tsx
  • packages/sdk/src/hooks/browser-rendering.ts
  • packages/sdk/src/mcp/index.ts
  • apps/web/src/components/integrations/apps.ts
  • packages/sdk/src/mcp/browser-rendering/api.ts
  • apps/web/src/components/browser-rendering/browser-rendering-view.tsx
  • apps/web/src/components/sidebar/index.tsx
  • packages/sdk/src/crud/browser-rendering.ts
  • apps/web/src/main.tsx
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Name React components and classes using PascalCase

Files:

  • apps/web/src/components/browser-rendering/browser-rendering-resource-list.tsx
  • apps/web/src/components/chat/provider.tsx
  • apps/web/src/components/browser-rendering/browser-rendering-view.tsx
  • apps/web/src/components/sidebar/index.tsx
  • apps/web/src/main.tsx
{apps/web,packages}/**/*.{tsx,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Keep Tailwind design tokens consistent with the design system

Files:

  • apps/web/src/components/browser-rendering/browser-rendering-resource-list.tsx
  • apps/web/src/components/chat/provider.tsx
  • apps/web/src/components/browser-rendering/browser-rendering-view.tsx
  • apps/web/src/components/sidebar/index.tsx
  • apps/web/src/main.tsx
supabase/**

📄 CodeRabbit inference engine (AGENTS.md)

Store Supabase migrations under supabase/

Files:

  • supabase/migrations/20251026110000_browser_rendering_and_fs_policies.sql
supabase/migrations/*_*!(_policies).sql

📄 CodeRabbit inference engine (.cursor/rules/api-development.mdc)

supabase/migrations/*_*!(_policies).sql: Create a migration to define the feature table with uuid primary key, workspace column, timestamps (created_at, updated_at), optional deleted_at, unique(name, workspace), and performance indexes
Enable Row Level Security (RLS) on all newly created tables via ALTER TABLE ... ENABLE ROW LEVEL SECURITY

Files:

  • supabase/migrations/20251026110000_browser_rendering_and_fs_policies.sql
supabase/migrations/*_*_policies.sql

📄 CodeRabbit inference engine (.cursor/rules/api-development.mdc)

supabase/migrations/*_*_policies.sql: Create a separate policies migration to insert policy records for view/manage resources and associate them with roles
Use sequential numeric IDs for policies and role_policies by finding the current max and incrementing
Assign role access: role 1 (owner) and role 4 (admin) manage_feature; roles 1,3,4 get view_feature

Files:

  • supabase/migrations/20251026110000_browser_rendering_and_fs_policies.sql
supabase/migrations/**/*.sql

📄 CodeRabbit inference engine (.cursor/rules/api-development.mdc)

Add appropriate database indexes on frequently queried columns (e.g., workspace, name) for performance

Files:

  • supabase/migrations/20251026110000_browser_rendering_and_fs_policies.sql
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/data-flow.mdc)

Log authorization failures to aid debugging and auditing

Files:

  • packages/sdk/src/hooks/browser-rendering.ts
  • packages/sdk/src/mcp/index.ts
  • apps/web/src/components/integrations/apps.ts
  • packages/sdk/src/mcp/browser-rendering/api.ts
  • packages/sdk/src/crud/browser-rendering.ts
packages/sdk/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/structure.mdc)

Place shared SDK code for data access and MCP integrations under packages/sdk/src

Files:

  • packages/sdk/src/hooks/browser-rendering.ts
  • packages/sdk/src/mcp/index.ts
  • packages/sdk/src/mcp/browser-rendering/api.ts
  • packages/sdk/src/crud/browser-rendering.ts
packages/**

📄 CodeRabbit inference engine (AGENTS.md)

Keep shared logic (UI kit, SDK, runtime, CLI tooling) under packages/

Files:

  • packages/sdk/src/hooks/browser-rendering.ts
  • packages/sdk/src/mcp/index.ts
  • packages/sdk/src/mcp/browser-rendering/api.ts
  • packages/sdk/src/crud/browser-rendering.ts
  • packages/sdk/package.json
packages/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Enforce kebab-case filenames in shared packages

Files:

  • packages/sdk/src/hooks/browser-rendering.ts
  • packages/sdk/src/mcp/index.ts
  • packages/sdk/src/mcp/browser-rendering/api.ts
  • packages/sdk/src/crud/browser-rendering.ts
packages/sdk/src/mcp/index.ts

📄 CodeRabbit inference engine (.cursor/rules/api-development.mdc)

Export new feature APIs by importing the module and adding its tools to WORKSPACE_TOOLS in packages/sdk/src/mcp/index.ts

Files:

  • packages/sdk/src/mcp/index.ts
packages/sdk/src/mcp/*/api.ts

📄 CodeRabbit inference engine (.cursor/rules/api-development.mdc)

packages/sdk/src/mcp/*/api.ts: Place new MCP API modules under packages/sdk/src/mcp/{feature}/api.ts following the shown file structure and imports
Use Zod schemas for all tool inputs and outputs in MCP API handlers
Use the QueryResult pattern for all database selects and map rows via typed mappers
Scope all queries to the current workspace and call assertWorkspaceResourceAccess and assertHasWorkspace before DB operations
Avoid SELECT *; define explicit SELECT query constants (e.g., SELECT_FEATURE_QUERY) and use them in DB operations
Validate user inputs (e.g., non-empty name) and throw typed errors like UserInputError for invalid inputs
Return list results wrapped in an object with array properties (e.g., { features: Feature[] })
Implement list operations with optional search and pagination support
Use createToolGroup to define tool groups with name, description, and icon
Use TypeScript types/interfaces for outputs and keep them aligned with Zod schemas
Leverage generated database types where available for stronger typing in queries and mappers
Implement role-based access control at the handler level by enforcing workspace and policy checks before performing operations
For list endpoints, implement ordering (e.g., created_at desc) and return mapped DTOs via mappers
Throw meaningful, typed errors and handle database errors gracefully (propagate or wrap)

Files:

  • packages/sdk/src/mcp/browser-rendering/api.ts
{apps/web,packages/{sdk,ui}}/package.json

📄 CodeRabbit inference engine (.cursor/rules/structure.mdc)

Maintain a package.json in each workspace (apps/web, packages/sdk, packages/ui)

Files:

  • packages/sdk/package.json
🧬 Code graph analysis (6)
apps/web/src/components/browser-rendering/browser-rendering-resource-list.tsx (5)
packages/sdk/src/hooks/store.tsx (1)
  • useSDK (36-44)
apps/web/src/components/sidebar/team-selector.tsx (1)
  • useCurrentTeam (36-56)
packages/sdk/src/views.ts (1)
  • View (110-125)
packages/sdk/src/hooks/use-track-native-view-visit.ts (1)
  • useTrackNativeViewVisit (17-57)
apps/web/src/components/browser-rendering/browser-rendering-view.tsx (1)
  • BrowserRenderingView (31-391)
apps/web/src/components/chat/provider.tsx (1)
packages/sdk/src/hooks/store.tsx (1)
  • queryClient (9-18)
packages/sdk/src/hooks/browser-rendering.ts (3)
packages/sdk/src/hooks/store.tsx (1)
  • useSDK (36-44)
packages/sdk/src/crud/browser-rendering.ts (11)
  • listScreenshots (162-172)
  • CaptureScreenshotInput (4-29)
  • captureScreenshot (61-71)
  • GeneratePdfInput (73-85)
  • generatePdf (96-103)
  • FetchHtmlInput (105-112)
  • fetchHtml (122-128)
  • ScrapeWebsiteInput (130-138)
  • scrapeWebsite (148-154)
  • DeleteScreenshotInput (174-177)
  • deleteScreenshot (179-189)
packages/sdk/src/mcp/browser-rendering/api.ts (2)
  • listScreenshots (576-668)
  • deleteScreenshot (671-712)
packages/sdk/src/mcp/browser-rendering/api.ts (4)
packages/sdk/src/mcp/context.ts (1)
  • getEnv (250-251)
packages/sdk/src/mcp/assertions.ts (2)
  • assertHasWorkspace (52-58)
  • assertHasUser (76-86)
packages/sdk/src/hosts.ts (1)
  • Hosts (1-8)
packages/sdk/src/crud/browser-rendering.ts (2)
  • listScreenshots (162-172)
  • deleteScreenshot (179-189)
apps/web/src/components/browser-rendering/browser-rendering-view.tsx (2)
packages/sdk/src/hooks/browser-rendering.ts (3)
  • useScreenshots (30-53)
  • useDeleteScreenshot (106-122)
  • useCaptureScreenshot (55-71)
apps/web/src/components/decopilot/thread-context-provider.tsx (1)
  • useSetThreadContextEffect (159-165)
packages/sdk/src/crud/browser-rendering.ts (3)
packages/sdk/src/locator.ts (1)
  • ProjectLocator (12-12)
packages/sdk/src/mcp/index.ts (1)
  • MCPClient (326-341)
packages/sdk/src/mcp/browser-rendering/api.ts (2)
  • listScreenshots (576-668)
  • deleteScreenshot (671-712)
🪛 GitHub Actions: Tests
apps/web/src/components/chat/provider.tsx

[error] 554-554: Cannot find name 'queryClient'.


[error] 555-555: Cannot find name 'locator'.


[error] 559-559: Cannot find name 'queryClient'.


[error] 560-560: Cannot find name 'locator'.

packages/sdk/src/mcp/browser-rendering/api.ts

[error] 9-9: TS2345: Argument of type '"BrowserRendering"' is not assignable to parameter of type '"Views" | "HTTP" | "AI" | "Agent" | "AgentSetup" | "APIKeys" | "Channel" | "Contracts" | "Databases" | "Deconfig" | "Documents" | "Email" | "FS" | "Hosting" | "Integration" | "KnowledgeBase" | ... 13 more ... | "Self"'.


[error] 71-71: TS2339: Property 'CF_API_BROWSER_RENDERING' does not exist on type '{ SUPABASE_SERVER_TOKEN: string; CF_DISPATCH_NAMESPACE: string; CF_ACCOUNT_ID: string; CF_API_TOKEN: string; SUPABASE_URL: string; DATABASE_URL: string; OPENROUTER_API_KEY: string; DECO_CHAT_API_JWT_PUBLIC_KEY?: any; ... 14 more ...; TESTING_CUSTOMER_ID?: string | ... 1 more ... | undefined; }'.


[error] 72-72: TS2339: Property 'CF_API_BROWSER_RENDERING' does not exist on type '{ SUPABASE_SERVER_TOKEN: string; CF_DISPATCH_NAMESPACE: string; CF_ACCOUNT_ID: string; CF_API_TOKEN: string; SUPABASE_URL: string; DATABASE_URL: string; OPENROUTER_API_KEY: string; DECO_CHAT_API_JWT_PUBLIC_KEY?: any; ... 14 more ...; TESTING_CUSTOMER_ID?: string | ... 1 more ... | undefined; }'.

🔇 Additional comments (9)
apps/api/.dev.vars.example (1)

25-25: LGTM!

The new environment variable is clearly documented and follows the existing pattern for Cloudflare configuration variables.

packages/sdk/package.json (1)

79-79: LGTM!

The new export follows the established pattern for SDK hook modules and is properly placed among other hook exports.

apps/web/src/components/sidebar/index.tsx (2)

465-465: LGTM!

The Browser item is properly added to the well-known items list, consistent with other native resource types.


514-523: LGTM!

The Browser native item configuration is well-structured and consistent with the pattern used for other native views. The explicit route path and comingSoon: false flag correctly indicate the feature is fully available.

packages/sdk/src/mcp/index.ts (2)

6-6: LGTM!

The import statement follows the established pattern for other API modules.


189-195: LGTM!

The browser rendering tools are properly registered in PROJECT_TOOLS with a clear comment. The tool set includes all expected operations: screenshot, PDF, HTML, scrape, list, and delete.

apps/web/src/components/integrations/apps.ts (2)

162-171: LGTM!

The native Browser app entry is well-defined with all required fields and follows the established pattern for other native apps.


182-182: LGTM!

The mapping entry correctly links the native app ID to its canonical title.

apps/web/src/main.tsx (1)

442-448: Fix undefined projectKey handling or remove org-level route.

The duplication is intentional—routes exist at both org (/:org/browser-rendering) and project (/:org/:project/browser-rendering) levels. However, the component derives projectKey from SDK locator and will receive undefined at the org level, resulting in a malformed viewPath: "/undefined/browser-rendering" passed to useTrackNativeViewVisit.

Either ensure the component explicitly handles the org-level context (undefined projectKey case), or remove the browser-rendering route from the org-level routes if it's only meant for project-level usage. This same pattern exists across other resource list components (agents, views, workflows, tools, documents), so verify whether this is accepted SDK behavior or a systematic issue.

Comment on lines +10 to +24
const projectKey = typeof locator === "string" ? locator : undefined;
const browserRenderingViewId = useMemo(() => {
const views = (team?.views ?? []) as View[];
const view = views.find((v) => v.title === "Browser");
return view?.id;
}, [team?.views]);

// Track visit for recents/pinning
useTrackNativeViewVisit({
viewId: browserRenderingViewId || "browser-rendering-fallback",
viewTitle: "Browser",
viewIcon: "camera",
viewPath: `/${projectKey}/browser-rendering`,
projectKey,
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential issue: Undefined projectKey in viewPath.

If locator is not a string, projectKey will be undefined, resulting in a viewPath like "/undefined/browser-rendering". This could cause issues with recent tracking and navigation.

Consider adding a guard or ensuring locator is always a string at this point:

 export function BrowserRenderingResourceList() {
   const { locator } = useSDK();
   const team = useCurrentTeam();
 
-  const projectKey = typeof locator === "string" ? locator : undefined;
+  const projectKey = typeof locator === "string" ? locator : "";
+  
+  if (!projectKey) {
+    console.warn("BrowserRenderingResourceList: No project context available");
+  }
+  
   const browserRenderingViewId = useMemo(() => {
     const views = (team?.views ?? []) as View[];
     const view = views.find((v) => v.title === "Browser");
     return view?.id;
   }, [team?.views]);
 
   // Track visit for recents/pinning
   useTrackNativeViewVisit({
     viewId: browserRenderingViewId || "browser-rendering-fallback",
     viewTitle: "Browser",
     viewIcon: "camera",
     viewPath: `/${projectKey}/browser-rendering`,
     projectKey,
   });

Alternatively, verify if this component should only be rendered when projectKey is guaranteed to be available.

🤖 Prompt for AI Agents
In apps/web/src/components/browser-rendering/browser-rendering-resource-list.tsx
around lines 10 to 24, projectKey can be undefined when locator is not a string
which produces a viewPath of "/undefined/browser-rendering"; update the code to
avoid embedding "undefined" by either (a) computing a safeProjectKey = typeof
locator === "string" ? locator : "" and use
`/${safeProjectKey}/browser-rendering` so paths never contain "undefined", or
(b) only call useTrackNativeViewVisit when projectKey is a string (skip tracking
when missing) so viewPath is only built for valid projects; implement one of
these guards consistently and ensure viewPath and projectKey passed to the hook
are valid.

Comment on lines +345 to +349
onClick={(e) => {
e.stopPropagation();
window.open(screenshot.url, "_blank");
}}
>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

window.open should use noopener,noreferrer

Prevent reverse tabnabbing and opener leaks.

- window.open(screenshot.url, "_blank");
+ window.open(screenshot.url, "_blank", "noopener,noreferrer");
🤖 Prompt for AI Agents
In apps/web/src/components/browser-rendering/browser-rendering-view.tsx around
lines 345 to 349, the onClick handler uses window.open(screenshot.url, "_blank")
which allows opener/tabnabbing; change it to open the new tab with noopener and
noreferrer and null out the opener. Specifically, call
window.open(screenshot.url, "_blank", "noopener,noreferrer") and then if the
returned window object is non-null set newWin.opener = null to ensure the new
page cannot access the opener; keep e.stopPropagation() as-is.

Comment on lines +403 to +407
// Get the start of the week (last Monday)
const weekStart = new Date(now);
weekStart.setDate(now.getDate() - now.getDay() + 1);
return `browser-rendering/screenshots/${year}/${month}`;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

“This Week” doesn’t filter by week

You compute weekStart but return the month prefix, so “This Week” == “This Month”. Either implement client-side 7‑day filtering or rename. Suggest filtering by lastModified.

-  const filteredScreenshots = useMemo(() => {
-    if (!deferredSearchTerm) return screenshots;
-
-    const lowerSearch = deferredSearchTerm.toLowerCase();
-    return screenshots.filter(
-      (screenshot) =>
-        screenshot.metadata?.sourceUrl?.toLowerCase().includes(lowerSearch) ||
-        screenshot.path.toLowerCase().includes(lowerSearch),
-    );
-  }, [screenshots, deferredSearchTerm]);
+  const filteredScreenshots = useMemo(() => {
+    const now = Date.now();
+    const inLast = (ms: number, t: string) =>
+      now - new Date(t).getTime() <= ms;
+
+    let items = screenshots;
+    if (dateFilter === "week") {
+      items = items.filter((s) => inLast(7 * 24 * 60 * 60 * 1000, s.lastModified));
+    }
+
+    if (!deferredSearchTerm) return items;
+    const lower = deferredSearchTerm.toLowerCase();
+    return items.filter(
+      (s) =>
+        s.metadata?.sourceUrl?.toLowerCase().includes(lower) ||
+        s.path.toLowerCase().includes(lower),
+    );
+  }, [screenshots, dateFilter, deferredSearchTerm]);

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

🤖 Prompt for AI Agents
In apps/web/src/components/browser-rendering/browser-rendering-view.tsx around
lines 403–407, the code computes weekStart but returns the month path, so the
"This Week" view shows the entire month; update the logic to actually filter by
week: keep/generate the same storage prefix if needed but filter the resulting
items client-side by parsing each item's lastModified (or equivalent timestamp)
into a Date and only include items where lastModified >= weekStart (i.e., within
the last 7 days); ensure timezone-safe Date comparisons and update the UI to
render the filtered list. Alternatively, if you prefer not to implement
filtering, rename the view from "This Week" to "This Month" to match the current
behavior.

Comment on lines +551 to +562
// Handle browser screenshot captures - refresh gallery
if (toolCall.toolName === "BROWSER_SCREENSHOT") {
// Invalidate all screenshot queries to refresh the gallery
queryClient.invalidateQueries({
queryKey: ["browser-rendering", "screenshots", locator],
});

// Force immediate refetch
queryClient.refetchQueries({
queryKey: ["browser-rendering", "screenshots", locator],
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Missing imports and variable definitions.

The code references queryClient and locator which are not defined in scope, causing pipeline test failures.

Apply this diff to fix the missing dependencies:

 import { useThreadContext } from "../decopilot/thread-context-provider.tsx";
+import { queryClient } from "@deco/sdk/hooks/store";
 
 interface UiOptions {

Then update the handler to obtain the locator from context:

     onToolCall: ({ toolCall }) => {
       // Trigger all registered tool call listeners
       triggerToolCallListeners(toolCall);
 
       // Handle RENDER tool
       if (toolCall.toolName === "RENDER") {
         const { content, title } = (toolCall.input ?? {}) as {
           content?: string;
           title?: string;
         };
 
         const isImageLike = content && IMAGE_REGEXP.test(content);
 
         if (!isImageLike) {
           openPreviewPanel(
             `preview-${toolCall.toolCallId}`,
             content || "",
             title || "",
           );
         }
       }
 
       // Handle browser screenshot captures - refresh gallery
       if (toolCall.toolName === "BROWSER_SCREENSHOT") {
+        // Get locator from agent metadata or context
+        const projectLocator = agentRoot;
+        
         // Invalidate all screenshot queries to refresh the gallery
         queryClient.invalidateQueries({
-          queryKey: ["browser-rendering", "screenshots", locator],
+          queryKey: ["browser-rendering", "screenshots", projectLocator],
         });
 
         // Force immediate refetch
         queryClient.refetchQueries({
-          queryKey: ["browser-rendering", "screenshots", locator],
+          queryKey: ["browser-rendering", "screenshots", projectLocator],
         });
       }

Note: Verify that agentRoot is the correct locator value for the screenshot queries, as it represents the workspace context.

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

🧰 Tools
🪛 GitHub Actions: Tests

[error] 554-554: Cannot find name 'queryClient'.


[error] 555-555: Cannot find name 'locator'.


[error] 559-559: Cannot find name 'queryClient'.


[error] 560-560: Cannot find name 'locator'.

🤖 Prompt for AI Agents
In apps/web/src/components/chat/provider.tsx around lines 551 to 562, the
snippet uses queryClient and locator which are not defined; import and
initialize the query client and obtain the locator from the handler context: add
an import for the query-client hook used in this repo (e.g. useQueryClient from
@tanstack/react-query or react-query) at the top of the file, call that hook
inside the component/handler to get const queryClient = useQueryClient(), and
derive locator from the handler/context (e.g. const locator = context?.agentRoot
|| agentRootFallback) before using it in invalidateQueries/refetchQueries;
ensure you add a sensible fallback if agentRoot is undefined and verify
agentRoot is the intended workspace locator.

Comment on lines +37 to +47
try {
return await listScreenshots({
locator,
prefix: options?.prefix,
limit: options?.limit,
});
} catch (error) {
console.error("Failed to list screenshots:", error);
// Return empty array on error instead of throwing
return [];
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don’t swallow query errors; let React Query surface them

Returning [] masks failures and prevents isError UI. Remove try/catch and throw so consumers can render error states.

-    queryFn: async () => {
-      if (!locator) throw new Error("No locator available");
-      try {
-        return await listScreenshots({
-          locator,
-          prefix: options?.prefix,
-          limit: options?.limit,
-        });
-      } catch (error) {
-        console.error("Failed to list screenshots:", error);
-        // Return empty array on error instead of throwing
-        return [];
-      }
-    },
+    queryFn: async () => {
+      if (!locator) throw new Error("No locator available");
+      return await listScreenshots({
+        locator,
+        prefix: options?.prefix,
+        limit: options?.limit,
+      });
+    },
📝 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
try {
return await listScreenshots({
locator,
prefix: options?.prefix,
limit: options?.limit,
});
} catch (error) {
console.error("Failed to list screenshots:", error);
// Return empty array on error instead of throwing
return [];
}
return await listScreenshots({
locator,
prefix: options?.prefix,
limit: options?.limit,
});
🤖 Prompt for AI Agents
In packages/sdk/src/hooks/browser-rendering.ts around lines 37 to 47, the
current try/catch swallows errors by returning an empty array which prevents
React Query from marking the request as failed; remove the try/catch (or replace
it with a catch that rethrows the error) so that errors from listScreenshots
propagate to callers, allowing React Query to surface isError UI and error
states.

Comment on lines +71 to +73
if (env.CF_API_BROWSER_RENDERING) {
headers["Authorization"] = `Bearer ${env.CF_API_BROWSER_RENDERING}`;
} else if (env.CF_API_TOKEN) {
Copy link
Contributor

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

Type error: add CF_API_BROWSER_RENDERING to EnvVars schema

The env type lacks CF_API_BROWSER_RENDERING. Update envSchema in packages/sdk/src/mcp/context.ts.

Patch example:

-  CF_API_TOKEN: z.string(),
+  CF_API_TOKEN: z.string(),
+  CF_API_BROWSER_RENDERING: z.string().optional(),

As per coding guidelines


🏁 Script executed:

#!/bin/bash
# Locate env schema and confirm missing key
rg -n 'envSchema' packages/sdk/src/mcp -C3
rg -n 'CF_API_BROWSER_RENDERING' packages -C1 || true

Length of output: 1688


I need to see the full envSchema definition to verify if CF_API_BROWSER_RENDERING is already present. The script output was truncated. Let me retrieve the complete schema.


---

🏁 Script executed:

```shell
#!/bin/bash
# Display full envSchema definition
sed -n '219,248p' packages/sdk/src/mcp/context.ts

Length of output: 1218


Add CF_API_BROWSER_RENDERING to envSchema in packages/sdk/src/mcp/context.ts

The CF_API_BROWSER_RENDERING environment variable is used in the browser-rendering API but missing from the envSchema definition, causing a type error. Add the field as optional to match the conditional usage pattern:

  TESTING_CUSTOMER_ID: z.string().nullish(),
+  CF_API_BROWSER_RENDERING: z.string().optional().readonly(),
});
📝 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
if (env.CF_API_BROWSER_RENDERING) {
headers["Authorization"] = `Bearer ${env.CF_API_BROWSER_RENDERING}`;
} else if (env.CF_API_TOKEN) {
TESTING_CUSTOMER_ID: z.string().nullish(),
CF_API_BROWSER_RENDERING: z.string().optional().readonly(),
});
🧰 Tools
🪛 GitHub Actions: Tests

[error] 71-71: TS2339: Property 'CF_API_BROWSER_RENDERING' does not exist on type '{ SUPABASE_SERVER_TOKEN: string; CF_DISPATCH_NAMESPACE: string; CF_ACCOUNT_ID: string; CF_API_TOKEN: string; SUPABASE_URL: string; DATABASE_URL: string; OPENROUTER_API_KEY: string; DECO_CHAT_API_JWT_PUBLIC_KEY?: any; ... 14 more ...; TESTING_CUSTOMER_ID?: string | ... 1 more ... | undefined; }'.


[error] 72-72: TS2339: Property 'CF_API_BROWSER_RENDERING' does not exist on type '{ SUPABASE_SERVER_TOKEN: string; CF_DISPATCH_NAMESPACE: string; CF_ACCOUNT_ID: string; CF_API_TOKEN: string; SUPABASE_URL: string; DATABASE_URL: string; OPENROUTER_API_KEY: string; DECO_CHAT_API_JWT_PUBLIC_KEY?: any; ... 14 more ...; TESTING_CUSTOMER_ID?: string | ... 1 more ... | undefined; }'.

🤖 Prompt for AI Agents
In packages/sdk/src/mcp/context.ts (update envSchema to include
CF_API_BROWSER_RENDERING), the envSchema is missing the CF_API_BROWSER_RENDERING
variable referenced in packages/sdk/src/mcp/browser-rendering/api.ts lines
~71-73; add CF_API_BROWSER_RENDERING as an optional string field to the
envSchema (matching the conditional usage), update the type/interface
accordingly so TypeScript recognizes it, and run type checks to ensure no other
references need updating.

Comment on lines +128 to +133
// @ts-expect-error - Return type mismatch with dimensions being optional
handler: async (props, c: AppContext) => {
// TODO: Uncomment after migration is applied
// await assertWorkspaceResourceAccess(c);
c.resourceAccess.grant();

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Enforce workspace access before grant; add auth consistently

Per guideline, perform authorization checks first, then c.resourceAccess.grant().

-  // TODO: Uncomment after migration is applied
-  // await assertWorkspaceResourceAccess(c);
-  c.resourceAccess.grant();
+  await assertHasWorkspace(c);
+  // TODO: remove after rollout gate if needed
+  await assertWorkspaceResourceAccess(c);
+  c.resourceAccess.grant();

Apply to all handlers (SCREENSHOT, PDF, HTML, SCRAPE, LIST, DELETE). Log authorization failures where applicable. As per coding guidelines

Also applies to: 350-354, 477-481, 534-538, 613-616, 680-698

🤖 Prompt for AI Agents
In packages/sdk/src/mcp/browser-rendering/api.ts around lines 128-133 (and also
update handlers at 350-354, 477-481, 534-538, 613-616, 680-698), the handlers
call c.resourceAccess.grant() before performing authorization checks; move the
authorization call (uncomment and await assertWorkspaceResourceAccess(c) or
equivalent) to run before c.resourceAccess.grant(), and ensure you catch and log
authorization failures with a clear message (using the existing logger) and
return/throw appropriately so grant() is never invoked on unauthorized requests;
apply the same change to all listed handlers and ensure consistent error logging
per coding guidelines.

Comment on lines +702 to +706
// 6. Delete metadata file if it exists
const metadataPath = path.replace(/\.(png|jpeg|pdf)$/, ".meta.json");
try {
await deleteFile.handler({ path: metadataPath });
} catch {
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Also delete .jpg metadata

The regex misses .jpg; jpg files won’t have their metadata removed.

-    const metadataPath = path.replace(/\.(png|jpeg|pdf)$/, ".meta.json");
+    const metadataPath = path.replace(/\.(png|jpe?g|pdf)$/, ".meta.json");
🤖 Prompt for AI Agents
In packages/sdk/src/mcp/browser-rendering/api.ts around lines 702 to 706, the
regex that builds metadataPath omits .jpg so .jpg.meta.json files are not
deleted; update the regex to include jpg (preferably use a single pattern like
/\.(png|jpe?g|pdf)$/i to match .jpg and .jpeg in a case-insensitive way) so
metadataPath correctly becomes .meta.json for .jpg files as well.

@@ -0,0 +1,52 @@
-- BROWSER RENDERING and FILE SYSTEM policies for screenshots, PDFs, and file management
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Rename to follow policies migration convention

This file inserts policy records but isn’t suffixed with _policies.sql. Please rename to 20251026110000_browser_rendering_and_fs_policies.sql → 20251026110000_browser_rendering_and_fs_policies_policies.sql to match the guideline.

As per coding guidelines

🤖 Prompt for AI Agents
In supabase/migrations/20251026110000_browser_rendering_and_fs_policies.sql
around line 1, the migration filename does not follow the _policies.sql
convention; rename the file to
20251026110000_browser_rendering_and_fs_policies_policies.sql, update any
references to the old filename in migration manifests or deployment scripts, and
commit the renamed file so the migration ordering and naming convention are
preserved.

Comment on lines +2 to +22
INSERT INTO "public"."policies" ("id", "created_at", "name", "statements", "description", "team_id") VALUES
('76', '2025-10-26 11:00:00.000000+00', 'use_browser_rendering', ARRAY[
'{"effect":"allow","resource":"BROWSER_SCREENSHOT"}'::jsonb,
'{"effect":"allow","resource":"BROWSER_PDF"}'::jsonb,
'{"effect":"allow","resource":"BROWSER_HTML"}'::jsonb,
'{"effect":"allow","resource":"BROWSER_SCRAPE"}'::jsonb,
'{"effect":"allow","resource":"BROWSER_SCREENSHOTS_LIST"}'::jsonb
], 'Allow users to capture screenshots, generate PDFs, fetch HTML, and scrape websites using Cloudflare Browser Rendering API', null),
('77', '2025-10-26 11:00:00.000000+00', 'manage_browser_screenshots', ARRAY[
'{"effect":"allow","resource":"BROWSER_SCREENSHOT_DELETE"}'::jsonb
], 'Allow users to delete screenshots', null),
('78', '2025-10-26 11:00:00.000000+00', 'use_file_system', ARRAY[
'{"effect":"allow","resource":"FS_LIST"}'::jsonb,
'{"effect":"allow","resource":"FS_READ"}'::jsonb,
'{"effect":"allow","resource":"FS_WRITE"}'::jsonb,
'{"effect":"allow","resource":"FS_READ_METADATA"}'::jsonb
], 'Allow users to list, read, write, and read metadata from file system (R2 storage)', null),
('79', '2025-10-26 11:00:00.000000+00', 'delete_files', ARRAY[
'{"effect":"allow","resource":"FS_DELETE"}'::jsonb
], 'Allow users to delete files from file system', null)
ON CONFLICT (id) DO NOTHING;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid hard-coded IDs; compute sequential IDs atomically

Hard-coding ids (76–79, 397–406) risks collisions across environments. Use MAX(id)+offset or insert/return and reuse per the guideline.

Apply a safer pattern like:

-INSERT INTO "public"."policies" ("id", "created_at", "name", "statements", "description", "team_id") VALUES 
-('76', '2025-10-26 11:00:00.000000+00', 'use_browser_rendering', ARRAY[ ... ]),
-('77', '2025-10-26 11:00:00.000000+00', 'manage_browser_screenshots', ARRAY[ ... ]),
-('78', '2025-10-26 11:00:00.000000+00', 'use_file_system', ARRAY[ ... ]),
-('79', '2025-10-26 11:00:00.000000+00', 'delete_files', ARRAY[ ... ])
-ON CONFLICT (id) DO NOTHING;
+WITH ins AS (
+  INSERT INTO public.policies (created_at, name, statements, description, team_id)
+  VALUES
+  (NOW(), 'use_browser_rendering', ARRAY[
+    '{"effect":"allow","resource":"BROWSER_SCREENSHOT"}'::jsonb,
+    '{"effect":"allow","resource":"BROWSER_PDF"}'::jsonb,
+    '{"effect":"allow","resource":"BROWSER_HTML"}'::jsonb,
+    '{"effect":"allow","resource":"BROWSER_SCRAPE"}'::jsonb,
+    '{"effect":"allow","resource":"BROWSER_SCREENSHOTS_LIST"}'::jsonb
+  ], 'Allow users to capture screenshots, generate PDFs, fetch HTML, and scrape websites using Cloudflare Browser Rendering API', null),
+  (NOW(), 'manage_browser_screenshots', ARRAY[
+    '{"effect":"allow","resource":"BROWSER_SCREENSHOT_DELETE"}'::jsonb
+  ], 'Allow users to delete screenshots', null),
+  (NOW(), 'use_file_system', ARRAY[
+    '{"effect":"allow","resource":"FS_LIST"}'::jsonb,
+    '{"effect":"allow","resource":"FS_READ"}'::jsonb,
+    '{"effect":"allow","resource":"FS_WRITE"}'::jsonb,
+    '{"effect":"allow","resource":"FS_READ_METADATA"}'::jsonb
+  ], 'Allow users to list, read, write, and read metadata from file system (R2 storage)', null),
+  (NOW(), 'delete_files', ARRAY[
+    '{"effect":"allow","resource":"FS_DELETE"}'::jsonb
+  ], 'Allow users to delete files from file system', null)
+  ON CONFLICT (name) DO NOTHING
+  RETURNING id, name
+),
+pol AS (
+  SELECT id, name FROM ins
+  UNION ALL
+  SELECT id, name FROM public.policies
+  WHERE name IN ('use_browser_rendering','manage_browser_screenshots','use_file_system','delete_files')
+)
+INSERT INTO public.role_policies (id, created_at, role_id, policy_id)
+SELECT
+  (SELECT COALESCE(MAX(id),0) FROM public.role_policies) + seq AS id,
+  NOW(),
+  rp.role_id,
+  (SELECT id FROM pol WHERE name = rp.policy_name)
+FROM (VALUES
+  (1, 'use_browser_rendering'),
+  (3, 'use_browser_rendering'),
+  (4, 'use_browser_rendering'),
+  (1, 'manage_browser_screenshots'),
+  (4, 'manage_browser_screenshots'),
+  (1, 'use_file_system'),
+  (3, 'use_file_system'),
+  (4, 'use_file_system'),
+  (1, 'delete_files'),
+  (4, 'delete_files')
+) AS rp(role_id, policy_name)
+JOIN generate_series(1,10) AS g(seq) ON TRUE
+ON CONFLICT (id) DO NOTHING;

Optionally add unique indexes on (name) in policies and (role_id, policy_id) in role_policies if not present. Based on learnings

Also applies to: 25-51

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.

3 participants