npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@synod-ai/extension-feedback

v0.1.6

Published

Synod feedback extension core SDK — HTTP client, lifecycle management, and React wrapper for submitting external findings.

Downloads

291

Readme

@synod-ai/extension-feedback

Synod Feedback Extension SDK — submit in-app feedback as structured findings to a Synod gateway instance. Captures element context (CSS selector, viewport, route), optional screenshots, and queues findings for batch submission with retry.

Installation

npm install @synod-ai/extension-feedback

Peer Dependencies

  • React ≥18 and react-dom ≥18 (only if using the React wrapper)
  • The core SDK is framework-agnostic — zero React imports

Quick Start

Vanilla JavaScript

import { init, enable, queue, sendBatch } from '@synod-ai/extension-feedback';

// 1. Configure with your gateway URL, project ID, and extension token
init({
  gatewayUrl: 'http://localhost:8787',
  projectId: 'your-project-id',
  token: 'your-extension-token',
});

// 2. Enable the feedback overlay (key-combo listener activates)
enable();

// 3. Queue a finding manually (or use the built-in element picker via Ctrl+Shift+E)
queue({
  title: 'Button contrast too low',
  description: 'The checkout button fails WCAG AA contrast at 2.4:1',
  severity: 'high',
  context_json: {
    url: 'https://shop.example.com/checkout',
    css_selector: '#checkout-btn',
    element_text: 'Pay Now',
    viewport: { width: 1440, height: 900 },
    route: '/checkout',
  },
});

// 4. Submit batch to the gateway
const result = await sendBatch();
console.log(`Created: ${result.created.length}, Errors: ${result.errors.length}`);

React

import { FeedbackProvider, useFeedback } from '@synod-ai/extension-feedback/react';

// Wrap your app
function App() {
  return (
    <FeedbackProvider
      config={{
        gatewayUrl: 'http://localhost:8787',
        projectId: 'your-project-id',
        token: 'your-extension-token',
        keyCombo: 'Ctrl+Shift+E',
      }}
      autoEnable
    >
      <YourApp />
    </FeedbackProvider>
  );
}

// Use the hook in any child component
function FeedbackButton() {
  const { isEnabled, enable, disable, sendBatch } = useFeedback();

  return (
    <button onClick={() => (isEnabled ? disable() : enable())}>
      {isEnabled ? 'Disable Feedback' : 'Enable Feedback'}
    </button>
  );
}

Configuration

FeedbackConfig

| Field | Type | Required | Default | Description | |---|---|---|---|---| | gatewayUrl | string | Yes (defaults if omitted) | http://localhost:8787 | Synod gateway base URL | | projectId | string | Yes | — | Target project ID | | token | string | Yes | — | Extension auth token (generated in Desktop → Extensions settings tab) | | keyCombo | string | No | Ctrl+Shift+E | Key combo to activate the feedback overlay (hold ~500ms) | | timeout | number | No | 15000 | Request timeout in ms | | maxRetries | number | No | 3 | Max retry attempts on transient failures |

Key Combo Format

The keyCombo string uses +-separated modifiers + key:

Ctrl+Shift+E    (default — avoids Ctrl+Shift+F browser fullscreen conflict)
Ctrl+Alt+F
Shift+Q

Supported modifiers: Ctrl (or Control), Shift, Alt.

Hold detection: the combo must be held for ~500ms to activate, preventing accidental triggers.


API Reference

Core Functions (@synod-ai/extension-feedback)

| Function | Description | |---|---| | init(config) | Initialize the SDK with gateway config | | enable() | Enable the feedback overlay + key-combo listener | | disable() | Disable the feedback overlay | | isEnabled() | Returns true if the overlay is active | | queue(finding) | Add a finding to the submission queue | | sendBatch() | Submit all queued findings to the gateway (returns Promise<BatchResult>) | | clearQueue() | Remove all findings from the queue | | getQueueLength() | Current queue size |

React (@synod-ai/extension-feedback/react)

| Export | Description | |---|---| | <FeedbackProvider config={...} autoEnable?> | Context provider that manages the FeedbackManager lifecycle | | useFeedback() | Hook returning { init, enable, disable, sendBatch, queue, isEnabled, enabled } |

Types

type FindingSeverity = 'low' | 'medium' | 'high';

interface FindingContextJson {
  url?: string;
  css_selector?: string;
  element_text?: string;
  viewport?: { width: number; height: number };
  route?: string;
  screenshot_data_url?: string;  // data:image/* URL, ≤10KB total context budget
}

interface FindingPayload {
  title: string;
  description: string;
  severity: FindingSeverity;
  suggested_action?: 'create_story';
  context_json: FindingContextJson;
}

Gateway Setup

1. Token Generation

Generate an extension token from the Synod Desktop app:

  1. Open Settings → Extensions tab
  2. Click Generate Token
  3. Copy the token and pass it to init({ token })

2. Project ID

Find your project ID in the Synod Desktop sidebar or via GET /v2/projects/active.


Network Topology

The extension SDK communicates with the Synod gateway via HTTP POST /v2/findings/external. The gateway URL determines how the extension connects.

Localhost (Default)

Browser (your app)  →  http://localhost:8787  →  Synod Gateway
  • Default mode — works for same-machine testing
  • Chrome and Firefox have a localhost mixed-content exemption: fetch() from an HTTPS page to http://localhost:* is allowed
  • No additional setup needed
  • Gateway URL: http://localhost:8787

LAN (Same Network)

Browser (other device)  →  http://192.168.x.x:8787  →  Synod Gateway (0.0.0.0 binding)
  • For testing from other devices on the same local network
  • Bind the gateway to 0.0.0.0 to accept LAN connections
  • Gateway URL: http://<your-lan-ip>:8787
  • Note: Only works if your app is served over HTTP. If your app is HTTPS, see Tunnel mode below.

Tunnel (HTTPS Apps / Remote Access)

Browser (HTTPS app)  →  https://your-tunnel.ngrok.io  →  Synod Gateway

When to use: Your web app is served over HTTPS and needs to call the gateway. Browsers block fetch() from HTTPS pages to HTTP endpoints (mixed content blocking). The localhost exemption does not apply to LAN IPs — only to localhost / 127.0.0.1.

Setup with ngrok:

# Expose the Synod gateway (default port 8787) via HTTPS
ngrok http 8787

# Copy the HTTPS forwarding URL (e.g., https://abc123.ngrok.io)
# Use it as the gatewayUrl:
init({
  gatewayUrl: 'https://abc123.ngrok.io',
  projectId: 'your-project-id',
  token: 'your-extension-token',
});

Setup with Cloudflare Tunnel:

cloudflared tunnel --url http://localhost:8787
# Copy the generated HTTPS URL

Mixed Content Guidance

| App URL | Gateway URL | Works? | Notes | |---|---|---|---| | http://localhost:3000 | http://localhost:8787 | ✅ | Same-origin HTTP, no issue | | https://localhost:3000 | http://localhost:8787 | ✅ | Localhost mixed-content exemption | | https://app.example.com | http://localhost:8787 | ❌ | Mixed content blocked — use tunnel | | https://app.example.com | http://192.168.x.x:8787 | ❌ | Mixed content blocked — use tunnel | | https://app.example.com | https://tunnel.ngrok.io | ✅ | HTTPS-to-HTTPS via tunnel |

Recommendation: For production HTTPS environments, always use a tunnel (ngrok or Cloudflare Tunnel) to provide an HTTPS endpoint for the gateway.


Context JSON

The context_json object captures browser context for each finding. All fields are optional; the SDK enforces a 64KB total budget (MAX_CONTEXT_JSON_BYTES = 65_536), of which the screenshot may consume up to 32KB (MAX_SCREENSHOT_BYTES).

| Field | Type | Description | |---|---|---| | url | string | Full page URL | | css_selector | string | CSS selector of the target element | | element_text | string | Text content of the target element | | viewport | { width, height } | Browser viewport dimensions | | route | string | Current route/pathname | | screenshot_data_url | string | Base64 data:image/* screenshot (pre-compressed to fit budget) | | user_agent | string | Browser user agent | | batch_id | string | Batch correlation ID | | extension_version | string | SDK version |

Screenshot Size Limit

The screenshot_data_url must fit within the 10KB total context_json budget. The SDK pre-compresses screenshots before queuing. If the serialized context exceeds 10KB, the gateway rejects the batch with a CONTEXT_TOO_LARGE error.


Error Handling

import { sendBatch } from '@synod-ai/extension-feedback';

try {
  const result = await sendBatch();
  // result.created: array of { id, title, severity }
  // result.errors: array of { index, error }
} catch (err) {
  // err.code: 'AUTH_ERROR' | 'RATE_LIMITED' | 'NETWORK_ERROR' | 'TIMEOUT' | 'SERVER_ERROR' | 'CONTEXT_TOO_LARGE'
  console.error(`[${err.code}] ${err.message}`);
  if (err.retryable) {
    // SDK already retries automatically (maxRetries), but you can re-queue
  }
}

| Error Code | Retryable | Description | |---|---|---| | AUTH_ERROR | No | Invalid or expired token | | RATE_LIMITED | Yes | Gateway rate limit hit (429) | | NETWORK_ERROR | Yes | Connection failed | | TIMEOUT | Yes | Request exceeded timeout | | SERVER_ERROR | Yes | Gateway 5xx response | | CONTEXT_TOO_LARGE | No | context_json exceeds 10KB budget |


License

MIT