Skip to content

per-project widget snippet: route by data-project#7

Merged
Dan-Cleary merged 1 commit into
mainfrom
feat/per-project-widget
May 8, 2026
Merged

per-project widget snippet: route by data-project#7
Dan-Cleary merged 1 commit into
mainfrom
feat/per-project-widget

Conversation

@Dan-Cleary
Copy link
Copy Markdown
Owner

@Dan-Cleary Dan-Cleary commented May 8, 2026

Summary

Each project gets its own widget snippet (visible on the project detail page) with `data-project=""` baked in. Feedback from a project's widget routes there directly — URL-pattern matching becomes a fallback for snippets that don't declare a project.

Why

  • Lower-friction install: drop the snippet, done. No URL patterns to maintain.
  • Works on URLs that don't fit nice patterns (localhost, ngrok, query-string variations).
  • Existing teams with URL patterns keep working (snippet wins, patterns fall back).

What changed

  • Schema: `ingestEvents.projectId` (optional). HTTP route validates team ownership before persisting.
  • Widget (`widget/src/index.ts`): reads `data-project` from the script tag, includes `projectId` in every POST body.
  • HTTP route (`convex/http.ts`): validates posted projectId via new `verifyProjectInTeam` internal query; drops mismatches silently.
  • Parser (`convex/parserDb.ts`): prefers `ingestEvent.projectId` over URL-pattern matching. `auditLog` now emits `routedBy: "snippet" | "url-pattern" | "router"`.
  • UI: `ProjectDetail` "no events yet" card renders the actual per-project snippet with copy button. `OnboardingTab` widget step drops the inline team-only snippet and points users to `projects → install the widget`.

Test plan

  • Create a project → detail page shows snippet with `data-project=""` and team secret
  • Copy + drop snippet on a test page; submit feedback → item lands in that project (audit log shows `routedBy: snippet`)
  • Drop a snippet without `data-project` on a URL matching an existing project's pattern → still routes correctly (audit log shows `routedBy: url-pattern`)
  • Submit a snippet with a stale/foreign projectId → falls back gracefully

Backward compatibility

  • Existing installs without `data-project` continue to work (URL-pattern fallback).
  • URL patterns stay configurable in the project edit form for that fallback path.

🤖 Generated with Claude Code


Note

Medium Risk
Changes widget ingestion and routing by persisting an optional projectId and preferring it over URL-pattern matching, which can affect where feedback is attributed. Includes schema + HTTP ingest updates, so regressions could mis-route or drop project tagging if validation or snippet usage is incorrect.

Overview
Adds per-project widget installation snippets and routing. The widget now reads an optional data-project attribute and posts projectId with each /ingest/widget event.

On the backend, ingestEvents gains optional projectId; the widget HTTP route validates that posted projectId belongs to the team (otherwise ignores it) and persists it on the ingest event. The parser then prefers this stored projectId for routing, falling back to URL-pattern matching, and audit logging now records routedBy as snippet vs url-pattern vs router.

UI updates shift onboarding guidance away from a single team snippet and surface a per-project snippet (with copy button) on the project detail page, prompting users to create the team widget secret first.

Reviewed by Cursor Bugbot for commit 80ed5c8. Bugbot is set up for automated code reviews on this repo. Configure here.

…atch

each project now has its own snippet (visible on the project detail
page) with `data-project="<projectId>"` baked in. feedback from a
project's widget routes there directly, no URL-pattern matching needed.

schema:
- ingestEvents: optional projectId field. set when the snippet
  declared `data-project`; the HTTP route validates team ownership
  before persisting, so the parser can trust it.

widget (widget/src/index.ts):
- reads `data-project` from the script tag
- includes `projectId` in every POST body

ingest (convex):
- http.ts: validates posted projectId belongs to the team via new
  `verifyProjectInTeam` internal query; silently drops it on
  mismatch (falls back to URL-pattern matching).
- ingest.recordWidget: accepts + persists optional projectId on the
  ingestEvent row.
- parserDb.persistItems: prefers ingestEvent.projectId over URL
  pattern matching. emits routedBy: "snippet" | "url-pattern" |
  "router" in the audit log.

ui:
- ProjectDetail's "no events yet" card now renders the actual
  snippet for that project (team secret + project id baked in),
  with a one-click copy button. when the team secret hasn't been
  created yet, it points users to settings instead.
- OnboardingTab widget step: drop the inline team-only snippet.
  it now nudges users toward `projects → install the widget` and
  keeps just the secret create/rotate button.

backward compatible: existing widget installs without
`data-project` keep routing via URL patterns. URL patterns stay
configurable in the project edit form.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 8, 2026

Warning

Rate limit exceeded

@Dan-Cleary has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 32 minutes and 41 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ba5d3aaf-9e3f-4cda-81e3-9914cc36dbdf

📥 Commits

Reviewing files that changed from the base of the PR and between 4fe2f48 and 80ed5c8.

📒 Files selected for processing (7)
  • app/src/tabs/OnboardingTab.tsx
  • app/src/tabs/ProjectsTab.tsx
  • convex/http.ts
  • convex/ingest.ts
  • convex/parserDb.ts
  • convex/schema.ts
  • widget/src/index.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/per-project-widget

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

❤️ Share

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

@Dan-Cleary Dan-Cleary merged commit d62590d into main May 8, 2026
3 checks passed
@Dan-Cleary Dan-Cleary deleted the feat/per-project-widget branch May 8, 2026 15:54
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is ON, but it could not run because the branch was deleted or merged before autofix could start.

Reviewed by Cursor Bugbot for commit 80ed5c8. Configure here.

</button>
</div>
);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

New SnippetBlock duplicates existing CodeBlock component

Low Severity

SnippetBlock in ProjectsTab.tsx duplicates the existing CodeBlock in OnboardingTab.tsx — both render a pre with code content and a copy button using identical clipboard logic (navigator.clipboard.writeText + setTimeout to reset "copied" state after 1400ms). Extracting a shared component would avoid inconsistent bug fixes and styling drift.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 80ed5c8. Configure here.

Dan-Cleary added a commit that referenced this pull request May 8, 2026
per-project widget snippet (#7) made URL-pattern routing the fallback
path. with no legacy projects to migrate, ripping it out:

- schema.projects: drop urlPatterns, description columns
- projects.upsert: drop urlPatterns + description args (60 → 50 LOC)
- projects.ts: drop resolveProjectFromUrl + matchUrl + globMatch +
  helpers (~70 LOC)
- parserDb.persistItems: drop URL-pattern fallback + widgetUrl arg.
  routedBy enum is now just "snippet" | "router".
- parser.ts: drop the widgetUrl wiring through to persistItems
- ProjectsTab: drop urlPatterns: [] / description: "" from upsert
  calls (create + edit)

widget events without data-project (misconfigured snippet, deleted
project) flow through to the semantic router, same as granola/zoom.
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.

1 participant