Skip to content
thedesignprojectPublic

About

CRRT πŸ₯•

Resources

Stars

11 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

244 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

CRRT πŸ₯•

codecov

CRRT πŸ₯• is visual feedback capture for React sites, plus a private reviewer console and a Proof-style agent bridge.

What changed

This repo is no longer just "a widget that stores comments."

It now has five product surfaces:

  • Public widget in src/
    • capture feedback on any page
    • submit comments to the public ingestion API
  • Reviewer dashboard in apps/dashboard/
    • triage comments
    • accept or reject feedback
    • create agent shares
    • copy prompts for Codex or Claude Code
  • Public landing/demo in apps/landing/
    • CRRT marketing page
    • live widget demo mounted against the same published widget source
  • Agent bridge API in api/v1/
    • expose page-scoped or selection-scoped feedback to coding agents
    • track presence, events, and implementation updates
  • Storybook addon and CLI in packages/crrt-storybook/
    • connect a Storybook 10 project to CRRT Pro with one command
    • capture component feedback and failed checks with authenticated story context

The old /api/comments route still exists as a compatibility path and for the smoke workflow. The new product flow uses /api/v1/....

Repo layout

  • src/
    • published widget package
  • apps/dashboard/
    • private reviewer UI
  • apps/landing/
    • public CRRT landing/demo site for the widget
  • api/
    • Vercel serverless API routes
  • packages/crrt-storybook/
    • published @thedesignproject/crrt-storybook addon and connection CLI
  • db/
    • Drizzle schema, migrations, seed
  • supabase/
    • legacy SQL (supabase/legacy/) and storage policies (supabase/policies/) β€” schema management has moved to db/

Public widget

Install:

bun add @thedesignproject/crrt

Usage:

import { FeedbackWidget } from '@thedesignproject/crrt'

export default function App() {
  return (
    <FeedbackWidget projectId="demo-project" />
  )
}

Props:

Prop Type Required Description
projectId string yes Project public key used for comment capture.
apiBase string no Base URL of the backend that serves the widget API. Defaults to https://crrt.ai/api. Override to point at a self-hosted deployment.
theme 'light' | 'dark' | 'system' no Widget appearance. Defaults to dark; system follows prefers-color-scheme.
disabled boolean no When true, the widget renders nothing β€” no UI, no listeners, no API calls. Defaults to false. Useful for turning the widget off per environment (e.g. production).

Guest feedback and CRRT login

The identity dialog offers Continue as guest (with a display name) or Log in to CRRT. Login opens the hosted dashboard and reuses an existing CRRT session. No additional widget props or Supabase credentials are needed. Self-hosted deployments must serve /dashboard/widget-auth on the same origin as apiBase and apply the generated widget-session migration.

The website receives an opaque, one-hour credential scoped to the signed-in user, project, and exact website origin. It stays in memory; reloading requires another handoff. The credential can create feedback and read, edit, or delete that user's widget comments on that origin. It cannot access dashboard or account APIs. The signed-in widget shows My feedback; the avatar opens account controls and Sign out of this widget, which revokes that widget session.

Public projects continue to offer guest feedback. In project settings, admins can enable Private project, which requires login and limits the website widget to the commenter's own feedback. Admins choose whether the entire project team or only the owner and admins can access all feedback in CRRT. Existing anonymous comments cannot be claimed by signing in.

New widget screenshots use private storage with five-minute signed URLs. Enabling privacy protects existing widget screenshots and removes their public objects before saving the setting. Automatic public agent links stop working for private projects; explicitly created feedback shares still grant access to their selected feedback. Apply the generated project-privacy migration before deploying this feature.

Sending feedback to GitHub explicitly exports its screenshot through a durable, screenshot-only link. That link stops working if the comment, screenshot, or linked GitHub issue is removed from CRRT. Privacy changes also cancel pending activity emails; each delivery rechecks recipient access.

The handoff uses a five-minute, one-time code bound to a proof generated by the widget. Only code and token hashes are stored in widget_auth_sessions, with RLS enabled. Expired rows are removed when a new handoff is created. No new signing-secret environment variable is required.

Widget authentication endpoints:

  • POST /v1/widget/auth/handoff requires a CRRT dashboard session and creates the one-time code.
  • POST /v1/widget/auth/exchange exchanges the code and proof for the restricted credential.
  • DELETE /v1/widget/auth/exchange revokes the presented widget credential.
  • GET /v1/widget/comments lists the credential owner's shared widget feedback on its origin.
  • PATCH /v1/widget/comments?id=... edits an owned comment's body.
  • DELETE /v1/widget/comments?id=... deletes an owned comment.
  • POST /v1/public/comments accepts the widget credential to record verified account ownership.

API surfaces

Public comments

The current widget calls these HTTP endpoints at apiBase:

  • GET /v1/public/comments?projectKey=... returns page comments for compatibility with the in-page sidebar.
  • POST /v1/public/comments creates a public comment.
  • PATCH /v1/public/comments updates review status for current widget compatibility.

Create comment request:

{
  "projectKey": "demo-project",
  "pageUrl": "https://example.com/pricing",
  "selector": "main > section:nth-of-type(2) button",
  "x": 540,
  "y": 220,
  "body": "This CTA feels too weak"
}

Patch status request:

{
  "id": "comment-id",
  "reviewStatus": "accepted"
}

Reviewer API

Protected by REVIEWER_API_TOKEN.

GET   /api/v1/projects
GET   /api/v1/projects/:projectId/comments
PATCH /api/v1/comments/:commentId/review-status
POST  /api/v1/feedback-shares
GET   /api/v1/feedback-shares/:shareId/prompt

Agent bridge

Protected by per-share bearer token.

GET  /api/v1/agent/shares/:slug/state
GET  /api/v1/agent/shares/:slug/events
POST /api/v1/agent/shares/:slug/presence
POST /api/v1/agent/shares/:slug/ops

Product Audit execution API

Product Audit runs a durable Explorer β†’ Critic β†’ Verifier workflow for a public URL. Explorer runs in an isolated Vercel Sandbox, model output is schema-validated through the existing OpenAI-compatible configuration, and deterministic admission can return zero to five Open findings.

GET  /api/v1/audits/capabilities
POST /api/v1/audits
GET  /api/v1/audits/:auditId
GET  /api/v1/audits/:auditId/events?after=0&limit=50
POST /api/v1/audits/:auditId/cancel

Creation requires Idempotency-Key. Project runs use a Supabase bearer token and projectKey. Anonymous runs use the returned signed X-Audit-Session value for subsequent creation and the per-audit X-Audit-Token for reads or cancellation; tokens are never placed in URLs. The server stores hashes only.

Set AUDIT_FEATURE_ENABLED=true, AUDIT_ANONYMOUS_ENABLED=true, and a random AUDIT_TOKEN_SECRET of at least 32 characters. The exact demo host demo.crrt.ai uses deterministic evidence without DNS or AI; every other URL must resolve publicly and uses the Vercel Workflow/Sandbox environment plus AI_API_BASE_URL, AI_API_KEY, and AI_MODEL. Set AI_CRITIC_MODEL to use a separate, faster Critic model; it falls back to AI_MODEL when omitted. Gateway 429 responses release the stage lease, back off durably, and finish as a partial zero-finding report if capacity remains unavailable. Never commit real secrets.

bun run local:dev runs live audit stages inline because its custom Bun API server does not expose Workflow SDK queue handlers. URL exploration still runs in an isolated node:24-alpine Docker container; pull that image before the first live local audit. The server binds only to loopback and lets unexpired anonymous reports reopen across local browser profiles without a capability token. Production always requires capability headers and uses Vercel Workflow and Vercel Sandbox.

Database schema

Schema is managed with Drizzle ORM. The source of truth is db/schema.ts, which defines:

  • projects
  • project_repo_configs
  • comments
  • feedback_shares
  • feedback_share_items
  • feedback_events
  • agent_presence
  • feedback_operation_keys

Applying the schema

  • Development: bun db:push β€” syncs the schema directly to your database. Fast iteration, skips migration history.
  • Production: bun db:migrate β€” applies versioned migrations from db/migrations/. deploy-build runs this on every deploy.

Generating a new migration

bun db:generate    # writes a new migration file under db/migrations/
bun db:migrate     # applies it

bun db:seed runs db/seed.ts, which inserts a demo-project row and a matching repo config for local development.

The hand-managed SQL files used before Drizzle live under supabase/legacy/ for reference only β€” do not run them against a current database.

Environment variables

For the default-off, test-only Stripe integration, see Stripe billing setup.

Example values live in .env.example.

Required server variables:

  • SUPABASE_URL
  • SUPABASE_KEY β€” the anon/publishable key; the dashboard includes it in the browser bundle
  • SUPABASE_SECRET_KEY β€” the privileged server-only key; never expose it to client code
  • During the key cutover, SUPABASE_SERVICE_ROLE_KEY is accepted as a fallback. Set SUPABASE_SECRET_KEY to a newly generated Supabase sb_secret_* key in each environment, deploy, and verify privileged operations before removing the old environment variable. Renaming the variable while keeping its legacy JWT value does not migrate the credential. Before deactivating legacy keys in Supabase, verify that no other apps, scripts, or public anon-key consumers still depend on them.
  • REVIEWER_API_TOKEN
  • SHARE_TOKEN_SECRET - encrypts persisted share and native integration tokens
  • WIDGET_AUTH_SECRET - signs native integration OAuth state

Product Audit server variables (when the feature is enabled):

  • AUDIT_FEATURE_ENABLED and AUDIT_ANONYMOUS_ENABLED - server-controlled availability
  • AUDIT_TOKEN_SECRET - signs anonymous browser sessions and capabilities
  • AUDIT_LOCAL_EXECUTION - local-only inline workflow adapter; ignored in production and set automatically by local:dev
  • AI_API_BASE_URL, AI_API_KEY, and AI_MODEL - OpenAI-compatible gateway configuration and Verifier/default model
  • AI_CRITIC_MODEL - optional Critic model override, for example openai/gpt-oss-20b; falls back to AI_MODEL
  • AUDIT_MAX_ROUTES, AUDIT_MAX_ACTIONS, AUDIT_WALL_CLOCK_MS, AUDIT_MODEL_TOKENS, and AUDIT_MAX_ARTIFACTS - optional bounded execution budgets
  • AUDIT_MODEL_TIMEOUT_MS and AUDIT_MODEL_ATTEMPTS - optional bounded provider retry controls

Optional server variables:

  • APP_URL - canonical app URL for generated links and notification emails

  • RESEND_API_KEY - enables comment activity emails through Resend

  • COMMENT_ACTIVITY_EMAIL_FROM - sender for comment activity emails, defaults to CRRT <activity@mail.crrt.ai>

  • COMMENT_ACTIVITY_EMAIL_COOLDOWN_HOURS - per-project email cooldown window, defaults to 5

  • COMMENT_ACTIVITY_EMAIL_TIMEOUT_MS - Resend request timeout, defaults to 5000

  • LINEAR_CLIENT_ID and LINEAR_CLIENT_SECRET - enable native Linear OAuth and issue creation

  • LINEAR_REDIRECT_URI - optional fixed Linear callback URL; defaults to APP_URL/v1/integrations/linear/callback

  • JIRA_CLIENT_ID and JIRA_CLIENT_SECRET - enable native Jira Cloud OAuth and issue creation

  • JIRA_REDIRECT_URI - optional fixed Jira callback URL; defaults to APP_URL/v1/integrations/jira/callback

The secret key also lets the API resolve project member emails from Supabase Auth for comment activity notifications. Without it, that email path stays disabled because there are no resolved recipients.

Comment activity notifications are persisted in the server-only comment_email_batches table before delivery. Each recipient gets a private message; the sender is never added as an extra recipient. Successful batches are checkpointed, and pending batches retry with the same frozen payload and Resend idempotency key. Retries stop after eight attempts or 20 hours, before Resend's 24-hour idempotency window ends; terminal failures retain a safe error code for inspection. The worker removes email content and recipient addresses older than seven days on successful runs.

The authenticated worker at GET /api/v1/internal/comment-email-deliveries runs every minute through Vercel Cron and requires Authorization: Bearer <CRON_SECRET>. Configure CRON_SECRET and use a Vercel plan supporting minute-level schedules. Self-hosted deployments must schedule the same authenticated request. Immediate delivery is attempted after enqueueing, but the scheduler is required for recovery after failures or process restarts. Apply the generated database migration before deploying this code.

Comment activity notifications are persisted in the server-only comment_email_batches table before delivery. Each recipient gets a private message; the sender is never added as an extra recipient. Successful batches are checkpointed, and pending batches retry with the same frozen payload and Resend idempotency key. Retries stop after eight attempts or 20 hours, before Resend's 24-hour idempotency window ends; terminal failures retain a safe error code for inspection. The worker removes email content and recipient addresses older than seven days on successful runs.

The authenticated worker at GET /api/v1/internal/comment-email-deliveries runs every minute through Vercel Cron and requires Authorization: Bearer <CRON_SECRET>. Configure CRON_SECRET and use a Vercel plan supporting minute-level schedules. Self-hosted deployments must schedule the same authenticated request. Immediate delivery is attempted after enqueueing, but the scheduler is required for recovery after failures or process restarts. Apply the generated database migration before deploying this code.

Useful client variables:

  • VITE_API_BASE
  • VITE_PROJECT_ID
  • VITE_REVIEWER_TOKEN

Requirements

  • React 18 or 19 on the consuming app.
  • A backend implementing the endpoints above.

Development

Install:

bun install

Run the public demo:

bun run dev

Run the reviewer dashboard:

bun run dev:dashboard

Fully local development with Docker

Docker Desktop and the Supabase CLI can run Postgres, Auth, Storage, Realtime, Studio, and local email without using a shared cloud project.

bun install
bun run local:up
bun run local:dev

local:up starts the containers, writes the local service credentials to the ignored .env.docker.local, applies every Drizzle migration, seeds demo-project, creates the public feedback-images bucket, and runs service smoke checks.

  • Dashboard: http://127.0.0.1:5173/dashboard/
  • API: http://127.0.0.1:3001
  • Supabase Studio: http://127.0.0.1:54323
  • Local email inbox: http://127.0.0.1:54324

Useful lifecycle commands:

bun run local:status
bun run local:down       # keeps local data
bun run local:reset      # deletes local data, then migrates and seeds again

The local Supabase credentials are development defaults and must never be used in production. Docker may publish the development ports beyond localhost, so run this stack only on a trusted network.

Build the widget package:

bun run build

Build the dashboard:

bun run build:dashboard

Run tests:

bun run test

Typecheck everything:

bun run typecheck

Compatibility

The legacy route remains available:

GET    /api/comments?projectId=...
POST   /api/comments
PATCH  /api/comments
DELETE /api/comments

That path is kept for backward compatibility and smoke validation. New product work should target /api/v1/....

Self-host

CRRT is OSS-first. crrt.ai is the easiest path β€” a managed hosted instance with auth, storage, and the agent bridge all wired up. But the same code in this repo runs on your own infra in under twenty minutes if you'd rather own the stack.

What you'll need

  • Postgres for the data layer. Any provider works; we recommend Supabase so you get auth + storage out of the box β€” that's what the hosted instance runs on.
  • A runtime that can serve the Vercel-style serverless functions in api/ plus the static builds in apps/landing/ and apps/dashboard/. We deploy to Vercel; Fly, Render, Cloudflare Workers, or a Node container behind a reverse proxy all work as long as your function runtime is compatible with @vercel/node.
  • Bun β‰₯ 1.4 for the build (bun install, bun run build).

1. Clone and install

git clone https://github.com/thedesignproject/CRRT.git
cd CRRT
bun install

2. Configure environment

Copy .env.example to .env:

cp .env.example .env

Fill in the required variables (see Environment variables):

  • SUPABASE_URL, SUPABASE_KEY β€” your Supabase URL and public anon/publishable key
  • SUPABASE_SECRET_KEY β€” your privileged server-only key; never expose it to client code
  • REVIEWER_API_TOKEN β€” long random string; gates the reviewer API endpoints
  • SHARE_TOKEN_SECRET β€” long random string; signs per-share bearer tokens

The dashboard reads SUPABASE_URL and SUPABASE_KEY at build time. Only the public key is included in the browser bundle.

3. Apply the database schema

bun run db:migrate

deploy-build does this on every deploy, but the first time you'll want to run it locally so the schema is in place before you push.

4. Deploy

If you're targeting Vercel:

vercel deploy

The vercel.json at the repo root sets buildCommand to bun run deploy-build, which typechecks, builds the landing app to apps/landing/dist, builds the dashboard into apps/landing/dist/dashboard, and runs pending migrations. Both surfaces ship in one deploy.

If you're targeting another runtime, the relevant outputs are:

  • apps/landing/dist/ β€” static landing page
  • apps/landing/dist/dashboard/ β€” static dashboard SPA (build with bun run build:dashboard)
  • api/ β€” Vercel-style serverless handlers; adapt them to your platform if needed

5. Smoke test

curl -s "$APP_URL/api/v1/public/comments?projectKey=demo-project"

You should get a JSON response (empty array on a fresh DB, or the seeded demo project's comments if you ran bun db:seed).

Updates

We tag widget releases on npm but the OSS server-side moves on trunk. Pull periodically; bun run db:migrate is idempotent so re-running it after a fetch is safe.

Versioning between hosted and self-hosted

crrt.ai runs whatever is on trunk. If you self-host, you can pin to a tag (git checkout v0.x.y) for stability. We aim to keep the public API surface backward-compatible across minor versions.

License

Licensed under the Apache License, Version 2.0. See LICENSE.

About

CRRT πŸ₯•

Resources

Stars

11 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages