Skip to content

feat: add agent output sharing support#613

Open
PeswikaBavagni-30 wants to merge 2 commits into
AditthyaSS:mainfrom
PeswikaBavagni-30:feat/share-outputs
Open

feat: add agent output sharing support#613
PeswikaBavagni-30 wants to merge 2 commits into
AditthyaSS:mainfrom
PeswikaBavagni-30:feat/share-outputs

Conversation

@PeswikaBavagni-30

@PeswikaBavagni-30 PeswikaBavagni-30 commented Jun 23, 2026

Copy link
Copy Markdown

#568
Currently, AgentRunner displays agent outputs, but users have no convenient way to share them. Users must manually copy text, which loses formatting, metadata, and context.

This Pull Request implements a complete sharing system with two options:

  1. Copy as formatted markdown card
  2. Create and copy a shareable public link with a 7-day expiration time.

Changes Made

1. Backend Server & Local Dev Middleware

  • Created Vercel serverless function api/share.js to handle POST /api/share. Validates inputs, generates UUID and 7-day expiration time, and stores the sharing record in the Supabase shared_outputs table.
  • Created Vercel serverless function api/share/[id].js to handle GET /api/share/:id. Fetches the record and validates that the sharing link has not expired.
  • Integrated configureServer middleware inside vite.config.js to route request paths /api/share and /api/share/:id in local development.

2. Frontend Changes

  • Modified src/lib/shareOutput.js to fetch from the newly added API endpoints instead of making direct client-side database calls.
  • Implemented a global toast notification component and hook in src/lib/useToast.jsx and wrapped the application inside src/main.jsx.
  • Modified src/components/ShareMenu.jsx to call useToast to trigger success alerts upon successfully copying the formatted card or creating a link.
  • Added a clean footer "Shared via AgentRunner" to src/pages/SharedOutputPage.jsx.
  • Added slide-in keyframe animations for toast alerts in src/index.css.

Database / Schema Updates

Table shared_outputs was created in Supabase with the following schema:

  • id (uuid primary key)
  • agent_id (text not null)
  • agent_name (text not null)
  • inputs (jsonb not null default '{}'::jsonb)
  • output (text not null)
  • output_type (text not null default 'markdown' check (output_type in ('markdown', 'text', 'json')))
  • created_at (timestamptz not null default now())
  • expires_at (timestamptz not null)

API Endpoints Added

  • POST /api/share: Create a new shared output. Expiry set to 7 days.
  • GET /api/share/:id: Get shared output if not expired. Returns 404 on expired or missing links.

Screenshots

1. Share Button Visible

Share Button Visible

2. Copy as Formatted Card

Copy as Card Workflow

3. Shareable Link Workflow

Shareable Link Workflow

4. Public Share Page

Public Share Page

Testing Performed

  • Ran the test suite via npm run test (all tests passed).
  • Ran production build via npm run build (compilation succeeded).
  • Manually verified via local Vite dev server and automated browser subagent that:
    • Copy as Card displays toast "Copied as formatted card".
    • Create shareable link creates the DB entry, copies the URL, and displays toast "Share link copied".
    • Share page is read-only, has no auth requirement, shows inputs, outputs, and the correct "Shared via AgentRunner" footer.
    • Verification video of the browser test flow is recorded.

Summary by CodeRabbit

  • New Features

    • Added ability to share agent outputs via time-limited shareable links (7-day expiration).
    • Introduced a share menu dropdown for quick link generation and clipboard copying.
    • Implemented toast notifications for user action feedback.
    • Created a dedicated page to view shared outputs with agent metadata and inputs.
  • Tests

    • Added test coverage for share card formatting utilities.

@vercel

vercel Bot commented Jun 23, 2026

Copy link
Copy Markdown

@PeswikaBavagni-30 is attempting to deploy a commit to the aditthyass' projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

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

To continue reviewing without waiting, enable usage-based billing in the billing tab.

⌛ How to resolve this issue?

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

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

🚦 How do rate limits work?

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

For paid Pro and Pro+ PR reviews, CodeRabbit uses rolling per-developer review limits. Reviews become available again as older review attempts age out of the rolling limit window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 5b17bf99-fa62-4d78-af76-e430bb572e45

📥 Commits

Reviewing files that changed from the base of the PR and between a9e532c and 4d427d9.

📒 Files selected for processing (1)
  • src/App.jsx

Note

.coderabbit.yaml has unrecognized properties

CodeRabbit is using all valid settings from your configuration. Unrecognized properties (listed below) have been ignored and may indicate typos or deprecated fields that can be removed.

⚠️ Parsing warnings (1)
Validation error: Unrecognized key: "issues"
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Walkthrough

Adds a complete agent output sharing feature: a Supabase shared_outputs table with 7-day expiry and RLS policies, POST/GET API handlers (production and Vite dev-server middleware), client-side formatting utilities and fetch wrappers, a ShareMenu dropdown replacing the previous share button in OutputRenderer, a ToastProvider notification system wired at the app root, and a public /share/:id page that renders shared outputs read-only.

Changes

Agent Output Sharing Feature

Layer / File(s) Summary
Database schema: shared_outputs table and RLS
supabase/migrations/20260623000000_create_shared_outputs.sql
Creates shared_outputs table (UUID PK, JSONB inputs, output_type constraint, 7-day expiry window check), enables RLS with insert/select policies for anon and authenticated roles, and adds an index on expires_at.
Share formatting utilities, API client, and tests
src/lib/shareOutputFormat.js, src/lib/shareOutput.js, test/shareOutput.test.js, package.json
shareOutputFormat.js exports SHARE_EXPIRY_DAYS, getShareableInputs, and buildShareCard. shareOutput.js provides createSharedOutput (POST) and getSharedOutput (GET), re-exporting the format utilities. Tests cover both formatting functions; node --test script is added to package.json.
Server-side API handlers
api/share.js, api/share/[id].js, vite.config.js
api/share.js handles POST with validation, inputs normalization, UUID/expiry generation, and Supabase insert. api/share/[id].js handles GET with expiry filtering. vite.config.js adds an api-server-middleware plugin replicating both handlers for local dev.
Toast notification system
src/lib/useToast.jsx, src/index.css, src/main.jsx
ToastProvider manages a toasts array, exposes addToast (auto-removes after 3 s) and removeToast, and renders an accessible fixed toast container. useToast hook enforces provider presence. CSS adds @keyframes toastIn and .animate-toast-in. main.jsx wraps the app with ToastProvider above AgentsProvider.
ShareMenu component and OutputRenderer toolbar update
src/components/ShareMenu.jsx, src/components/OutputRenderer.jsx, src/components/AgentRunner.jsx
ShareMenu adds a dropdown with "Copy Card" and "Create Link" actions, clipboard fallback, and toast feedback. OutputRenderer gains agent, inputs, and showToolbar props; the toolbar is gated by showToolbar and renders ShareMenu when agent is provided. AgentRunner passes agent and inputs to OutputRenderer.
SharedOutputPage and route registration
src/pages/SharedOutputPage.jsx, src/App.jsx
SharedOutputPage fetches shared output by id via getSharedOutput, handles loading/error/data states with unmount safety, and renders the output via OutputRenderer with showToolbar={false}. The /share/:id route is registered in App.jsx.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ShareMenu
    participant shareOutput.js
    participant APIHandler
    participant Supabase

    User->>ShareMenu: clicks "Create Link"
    ShareMenu->>shareOutput.js: createSharedOutput({agent, inputs, output})
    shareOutput.js->>APIHandler: POST /api/share {agentName, output, inputs}
    APIHandler->>Supabase: insert into shared_outputs
    Supabase-->>APIHandler: inserted row
    APIHandler-->>shareOutput.js: {shareId, url}
    shareOutput.js-->>ShareMenu: {id, url}
    ShareMenu->>User: copies URL to clipboard + shows toast

    rect rgba(99, 179, 237, 0.5)
        note over User,Supabase: Viewing a shared link
    end
    User->>SharedOutputPage: navigates to /share/:id
    SharedOutputPage->>shareOutput.js: getSharedOutput(id)
    shareOutput.js->>APIHandler: GET /api/share/:id
    APIHandler->>Supabase: select where id=? and expires_at > now()
    Supabase-->>APIHandler: row or null
    APIHandler-->>shareOutput.js: 200/404/500
    shareOutput.js-->>SharedOutputPage: sharedOutput or null/error
    SharedOutputPage->>User: renders OutputRenderer with showToolbar=false
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: add agent output sharing support' accurately summarizes the main feature addition: implementing a sharing system for agent outputs with both card copy and shareable link capabilities.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@mergify

mergify Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Hey @PeswikaBavagni-30! 👋
Wow — your first contribution to iloveAgents! This is a big deal and I want you to know it means a lot. 🎊
Every agent on this platform started exactly like this — someone like you deciding to spend their time building something useful for everyone. That is something to be proud of.
A few things while you wait for the review:

  • Star the repo if you haven't already. Star it here
  • 📖 Check the Contributing Guide
  • 💬 Drop a comment if you get stuck — I reply within 24 hours
    Can't wait to ship this with you. 🚀
    Welcome to the iloveAgents family. 🙏
    @AditthyaSS

@mergify

mergify Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

hey @PeswikaBavagni-30! 👋
Your PR doesn't seem to be linked to an issue.
Please add this line to your PR description:
Closes #issue_number
Replace issue_number with the actual issue number you are solving.
This helps us track what each PR is fixing! 🔗
@AditthyaSS

@mergify mergify Bot added the needs-fix label Jun 23, 2026
@mergify

mergify Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

⚠️ Hey @PeswikaBavagni-30! This PR has a merge conflict that needs to be resolved before we can review or merge it.
Please sync your branch with the latest main and fix the conflicts.
Need help? Check out resolving merge conflicts.
@AditthyaSS

@mergify

mergify Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

⚠️ This branch is out of date with main.
Please click "Update branch" to sync before merging.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (6)
api/share.js (3)

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

Consider sanitizing database error messages.

Returning error.message directly from Supabase could leak schema or constraint details to clients. For production, consider logging the full error server-side and returning a generic message like "Failed to create shared output" to clients.

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

In `@api/share.js` at line 61, The error response at line 61 in the error handling
block returns the raw `error.message` directly to the client, which could expose
sensitive database schema or constraint information. Modify this error handling
to log the full error object server-side for debugging purposes, while returning
a generic error message like "Failed to create shared output" to the client
instead of the raw error message.

27-32: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Consider validating output length and outputType before insert.

The handler validates that agentName and output are non-empty, but doesn't check:

  • output length (RLS policy enforces ≤100,000 chars)
  • outputType is one of 'markdown', 'text', 'json' (schema CHECK constraint enforces this)

If either constraint is violated, the insert will fail and return a generic 500 error. Validating in the handler would allow returning a clear 400 error with a specific message.

📋 Suggested validation additions
 if (!output || typeof output !== 'string' || !output.trim()) {
   return res.status(400).json({ error: 'output is required and must be a non-empty string' })
 }
+if (output.length > 100000) {
+  return res.status(400).json({ error: 'output must not exceed 100,000 characters' })
+}
+
+const validOutputTypes = ['markdown', 'text', 'json']
+if (outputType && !validOutputTypes.includes(outputType)) {
+  return res.status(400).json({ error: `outputType must be one of: ${validOutputTypes.join(', ')}` })
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/share.js` around lines 27 - 32, The validation block for agentName and
output is incomplete and missing checks for output length and outputType value.
Add validation after the existing non-empty string checks to verify that output
does not exceed 100,000 characters (enforced by the RLS policy) and that
outputType is one of the allowed values: 'markdown', 'text', or 'json' (enforced
by the schema CHECK constraint). Return a 400 status with a descriptive error
message for each validation failure, similar to the existing validation pattern,
so that constraint violations produce clear client errors instead of generic 500
errors on insert.

11-11: 🔒 Security & Privacy | 🔵 Trivial

Note: CORS allows all origins.

The Access-Control-Allow-Origin: * header permits any domain to create shared outputs via this endpoint. Combined with anonymous insert allowed by the RLS policy, this could be abused. The 100KB output limit and 7-day expiry in the RLS policy provide some protection, but consider whether you need additional safeguards (rate limiting, origin restrictions, or CAPTCHA for public endpoints).

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

In `@api/share.js` at line 11, The `Access-Control-Allow-Origin: *` header in the
setHeader call permits requests from any origin, which combined with anonymous
insert capabilities creates a security risk. Replace the wildcard '*' with
specific allowed origins that your application controls, or implement additional
safeguards such as rate limiting middleware, origin validation logic, or CAPTCHA
verification for the share endpoint to prevent abuse while maintaining
functionality.
vite.config.js (3)

24-26: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Supabase client recreated per request in dev middleware.

Line 26 creates a new Supabase client for every request to /api/share, while the production handlers (api/share.js, api/share/[id].js) create the client once at module scope. For better dev performance, consider moving client creation outside the request handler.

⚡ Suggested optimization
+const supabaseUrl = process.env.VITE_SUPABASE_URL || 'http://localhost:54321'
+const supabaseAnonKey = process.env.VITE_SUPABASE_ANON_KEY || 'dummy'
+const supabase = createClient(supabaseUrl, supabaseAnonKey)
+
 export default defineConfig({
   plugins: [
     react(),
     {
       name: 'api-server-middleware',
       configureServer(server) {
         server.middlewares.use(async (req, res, next) => {
           if (req.url && req.url.startsWith('/api/share')) {
             // ... CORS setup ...
-            const supabaseUrl = process.env.VITE_SUPABASE_URL || 'http://localhost:54321'
-            const supabaseAnonKey = process.env.VITE_SUPABASE_ANON_KEY || 'dummy'
-            const supabase = createClient(supabaseUrl, supabaseAnonKey)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@vite.config.js` around lines 24 - 26, The Supabase client initialization
(lines creating supabaseUrl, supabaseAnonKey, and calling createClient) is
currently happening inside the dev middleware request handler, causing a new
client to be instantiated for every request. Move the client creation logic
outside the middleware handler function so it executes once at module
initialization time, matching the pattern used in the production handlers
api/share.js and api/share/[id].js. This will improve dev server performance by
reusing the same client instance across requests instead of recreating it each
time.

40-51: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Consider adding the same validations as suggested for api/share.js.

The dev middleware duplicates the POST handler logic from api/share.js. The same validation improvements (output length check, outputType validation) would apply here. Keeping both implementations in sync ensures consistent behavior across dev and production environments.

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

In `@vite.config.js` around lines 40 - 51, The validation checks for agentName and
output in the dev middleware are missing the additional validations that exist
in api/share.js. Add a validation check for output length to ensure it meets the
minimum length requirement, and add a validation check for outputType to ensure
it is provided and matches an expected type. Place these additional validation
checks after the existing output validation check but following the same
pattern: verify the condition, set statusCode to 400, set the Content-Type
header to application/json, and return an error response with a descriptive
message before processing continues.

10-146: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Code duplication between dev middleware and production API handlers.

The middleware duplicates all logic from api/share.js (POST) and api/share/[id].js (GET). While necessary for dev vs production environments, this creates a maintenance burden—changes must be manually synchronized across three files. Consider documenting this relationship or exploring ways to share validation/payload logic.

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

In `@vite.config.js` around lines 10 - 146, The middleware in the configureServer
function duplicates all the validation logic and payload construction for both
the POST request (checking agentName, output, and parsing inputs) and the GET
request (database queries and error handling) that exists in the actual API
handlers. Extract the shared validation logic (such as agentName and output
validation), the payload construction logic, and the database query logic into
separate utility functions in a shared file that can be imported and reused by
both the middleware and the api/share.js and api/share/[id].js handlers. This
will centralize the logic and ensure changes only need to be made in one place
rather than across all three files.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/ShareMenu.jsx`:
- Around line 38-67: The setTimeout calls in handleCopyCard and handleCreateLink
are not tracked or cleaned up, creating race conditions and potential memory
leaks. Store timeout IDs in a useRef, clear any existing timeout before creating
a new one in both functions to prevent overlapping timeouts, and add a useEffect
cleanup function that clears the timeout when the component unmounts or
dependencies change.

In `@src/lib/useToast.jsx`:
- Around line 9-17: The addToast function needs two fixes: First, store the
timeout ID returned by setTimeout along with each toast object so that when
removeToast is called, the timeout can be cleared using clearTimeout to prevent
unnecessary state updates after manual dismissal. Second, replace the Date.now()
ID generation with a counter (incrementing on each call) or UUID library to
ensure unique IDs and prevent collisions when multiple toasts are added within
the same millisecond. Modify the toast object structure to include the timeoutId
and update the removeToast function to clear the timeout before removing the
toast from state.

---

Nitpick comments:
In `@api/share.js`:
- Line 61: The error response at line 61 in the error handling block returns the
raw `error.message` directly to the client, which could expose sensitive
database schema or constraint information. Modify this error handling to log the
full error object server-side for debugging purposes, while returning a generic
error message like "Failed to create shared output" to the client instead of the
raw error message.
- Around line 27-32: The validation block for agentName and output is incomplete
and missing checks for output length and outputType value. Add validation after
the existing non-empty string checks to verify that output does not exceed
100,000 characters (enforced by the RLS policy) and that outputType is one of
the allowed values: 'markdown', 'text', or 'json' (enforced by the schema CHECK
constraint). Return a 400 status with a descriptive error message for each
validation failure, similar to the existing validation pattern, so that
constraint violations produce clear client errors instead of generic 500 errors
on insert.
- Line 11: The `Access-Control-Allow-Origin: *` header in the setHeader call
permits requests from any origin, which combined with anonymous insert
capabilities creates a security risk. Replace the wildcard '*' with specific
allowed origins that your application controls, or implement additional
safeguards such as rate limiting middleware, origin validation logic, or CAPTCHA
verification for the share endpoint to prevent abuse while maintaining
functionality.

In `@vite.config.js`:
- Around line 24-26: The Supabase client initialization (lines creating
supabaseUrl, supabaseAnonKey, and calling createClient) is currently happening
inside the dev middleware request handler, causing a new client to be
instantiated for every request. Move the client creation logic outside the
middleware handler function so it executes once at module initialization time,
matching the pattern used in the production handlers api/share.js and
api/share/[id].js. This will improve dev server performance by reusing the same
client instance across requests instead of recreating it each time.
- Around line 40-51: The validation checks for agentName and output in the dev
middleware are missing the additional validations that exist in api/share.js.
Add a validation check for output length to ensure it meets the minimum length
requirement, and add a validation check for outputType to ensure it is provided
and matches an expected type. Place these additional validation checks after the
existing output validation check but following the same pattern: verify the
condition, set statusCode to 400, set the Content-Type header to
application/json, and return an error response with a descriptive message before
processing continues.
- Around line 10-146: The middleware in the configureServer function duplicates
all the validation logic and payload construction for both the POST request
(checking agentName, output, and parsing inputs) and the GET request (database
queries and error handling) that exists in the actual API handlers. Extract the
shared validation logic (such as agentName and output validation), the payload
construction logic, and the database query logic into separate utility functions
in a shared file that can be imported and reused by both the middleware and the
api/share.js and api/share/[id].js handlers. This will centralize the logic and
ensure changes only need to be made in one place rather than across all three
files.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 79a7652b-a730-4048-85f4-81e5583f1bdb

📥 Commits

Reviewing files that changed from the base of the PR and between 7d17a11 and a9e532c.

⛔ Files ignored due to path filters (5)
  • package-lock.json is excluded by !**/package-lock.json, !package-lock.json
  • screenshots/copy_as_card_workflow.png is excluded by !**/*.png
  • screenshots/public_share_page.png is excluded by !**/*.png
  • screenshots/share_button_visible.png is excluded by !**/*.png
  • screenshots/shareable_link_workflow.png is excluded by !**/*.png
📒 Files selected for processing (16)
  • api/share.js
  • api/share/[id].js
  • package.json
  • src/App.jsx
  • src/components/AgentRunner.jsx
  • src/components/OutputRenderer.jsx
  • src/components/ShareMenu.jsx
  • src/index.css
  • src/lib/shareOutput.js
  • src/lib/shareOutputFormat.js
  • src/lib/useToast.jsx
  • src/main.jsx
  • src/pages/SharedOutputPage.jsx
  • supabase/migrations/20260623000000_create_shared_outputs.sql
  • test/shareOutput.test.js
  • vite.config.js

Comment on lines +38 to +67
const handleCopyCard = async () => {
setError('')
try {
await copyText(buildShareCard({
agentName: agent.name,
inputs: getShareableInputs(inputs, agent.inputs),
output,
}))
setStatus('copied-card')
addToast('Copied as formatted card')
setTimeout(() => setStatus('idle'), 2000)
} catch {
setError('Could not copy the card. Please try again.')
}
}

const handleCreateLink = async () => {
setError('')
setStatus('creating-link')
try {
const shared = await createSharedOutput({ agent, inputs, output })
await copyText(shared.url)
setStatus('copied-link')
addToast('Share link copied')
setTimeout(() => setStatus('idle'), 2500)
} catch {
setStatus('idle')
setError('Could not create a shareable link. Please try again.')
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Clean up status timeouts to prevent race conditions.

Both handleCopyCard and handleCreateLink create setTimeout calls (lines 48, 62) that reset the status state, but these timeouts are not tracked or cleaned up. This causes:

  • Race conditions: Rapidly clicking either button multiple times creates overlapping timeouts that can reset status unexpectedly.
  • Memory leaks: If the component unmounts before the timeout fires, the setState call still executes.
🔧 Proposed fix
 export default function ShareMenu({ agent, inputs, output }) {
   const [open, setOpen] = useState(false)
   const [status, setStatus] = useState('idle')
   const [error, setError] = useState('')
   const menuRef = useRef(null)
+  const timeoutRef = useRef(null)
   const { addToast } = useToast()

   useEffect(() => {
     if (!open) return undefined
     const closeMenu = (event) => {
       if (!menuRef.current?.contains(event.target)) setOpen(false)
     }
     document.addEventListener('mousedown', closeMenu)
     return () => document.removeEventListener('mousedown', closeMenu)
   }, [open])
+
+  useEffect(() => {
+    return () => {
+      if (timeoutRef.current) clearTimeout(timeoutRef.current)
+    }
+  }, [])

   const handleCopyCard = async () => {
+    if (timeoutRef.current) clearTimeout(timeoutRef.current)
     setError('')
     try {
       await copyText(buildShareCard({
         agentName: agent.name,
         inputs: getShareableInputs(inputs, agent.inputs),
         output,
       }))
       setStatus('copied-card')
       addToast('Copied as formatted card')
-      setTimeout(() => setStatus('idle'), 2000)
+      timeoutRef.current = setTimeout(() => setStatus('idle'), 2000)
     } catch {
       setError('Could not copy the card. Please try again.')
     }
   }

   const handleCreateLink = async () => {
+    if (timeoutRef.current) clearTimeout(timeoutRef.current)
     setError('')
     setStatus('creating-link')
     try {
       const shared = await createSharedOutput({ agent, inputs, output })
       await copyText(shared.url)
       setStatus('copied-link')
       addToast('Share link copied')
-      setTimeout(() => setStatus('idle'), 2500)
+      timeoutRef.current = setTimeout(() => setStatus('idle'), 2500)
     } catch {
       setStatus('idle')
       setError('Could not create a shareable link. Please try again.')
     }
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/ShareMenu.jsx` around lines 38 - 67, The setTimeout calls in
handleCopyCard and handleCreateLink are not tracked or cleaned up, creating race
conditions and potential memory leaks. Store timeout IDs in a useRef, clear any
existing timeout before creating a new one in both functions to prevent
overlapping timeouts, and add a useEffect cleanup function that clears the
timeout when the component unmounts or dependencies change.

Comment thread src/lib/useToast.jsx
Comment on lines +9 to +17
const addToast = (message, type = 'success') => {
const id = Date.now()
setToasts((prev) => [...prev, { id, message, type }])

// Auto-remove after 3 seconds
setTimeout(() => {
removeToast(id)
}, 3000)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Clean up the auto-dismiss timeout to prevent memory leaks.

Two concerns:

  1. Timeout leak: When a toast is manually dismissed (line 49), the scheduled setTimeout still fires 3 seconds later, causing an unnecessary state update. If the component unmounts before the timeout, this triggers a React warning.

  2. ID collision risk: Date.now() can produce duplicate IDs if two toasts are added within the same millisecond (e.g., during rapid user actions or batch operations).

🔧 Proposed fix
 export function ToastProvider({ children }) {
   const [toasts, setToasts] = useState([])
+  const timeoutsRef = useRef(new Map())

   const addToast = (message, type = 'success') => {
-    const id = Date.now()
+    const id = `${Date.now()}-${Math.random()}`
     setToasts((prev) => [...prev, { id, message, type }])
     
     // Auto-remove after 3 seconds
-    setTimeout(() => {
+    const timeoutId = setTimeout(() => {
       removeToast(id)
     }, 3000)
+    timeoutsRef.current.set(id, timeoutId)
   }

   const removeToast = (id) => {
+    const timeoutId = timeoutsRef.current.get(id)
+    if (timeoutId) {
+      clearTimeout(timeoutId)
+      timeoutsRef.current.delete(id)
+    }
     setToasts((prev) => prev.filter((t) => t.id !== id))
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/useToast.jsx` around lines 9 - 17, The addToast function needs two
fixes: First, store the timeout ID returned by setTimeout along with each toast
object so that when removeToast is called, the timeout can be cleared using
clearTimeout to prevent unnecessary state updates after manual dismissal.
Second, replace the Date.now() ID generation with a counter (incrementing on
each call) or UUID library to ensure unique IDs and prevent collisions when
multiple toasts are added within the same millisecond. Modify the toast object
structure to include the timeoutId and update the removeToast function to clear
the timeout before removing the toast from state.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant