Cursor/multi event support 508c - #10
Conversation
Hosts post a night from /host. Each night gets /e/[slug] for the room QR. /, /apply, and /board still serve the featured night so the house QR keeps working. Co-authored-by: smile <smile@rayfernando.com>
Keep the userId and selectedAt notes. Those names still hide Clerk and board-order rules. Co-authored-by: smile <smile@rayfernando.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Stale comment
Risk: high. Left a non-blocking comment and did not approve: Cursor Bugbot reported unresolved findings, and this multi-event schema/API change is above the medium approval threshold. Human review is needed; no additional reviewers were assigned.
Sent by Cursor Approval Agent: Pull Request Router and Approver
Stick the house event from settings rather than the open-event fallback, keep submissions.eventId optional so existing rows can deploy, build /e/[slug] nav hrefs from the known slug while loading, and trim hyphens after slug truncation so long names still create.
There was a problem hiding this comment.
Stale comment
Risk: high. Left a non-blocking comment and did not approve: Cursor Bugbot completed cleanly with prior findings resolved, but this multi-event schema and host/apply/board change is above the medium approval threshold. Human review is needed; no reviewers were assigned.
Sent by Cursor Approval Agent: Pull Request Router and Approver
The Vercel preview was calling events:bySlug against a backend that never received this branch. Required eventId on existing submissions would also block a cloud deploy. Keep that field optional until ensureSeed backfills, persist the featured pointer in settings, and install Convex AI files plus the ESLint plugin so local convex dev can push and surface function errors. Co-authored-by: smile <smile@rayfernando.com>
There was a problem hiding this comment.
Stale comment
Risk: high. Left a non-blocking comment and did not approve: Cursor Bugbot skipped on this head, and this multi-event schema and host/apply/board change is above the medium approval threshold. Human review is needed; no reviewers were assigned.
Sent by Cursor Approval Agent: Pull Request Router and Approver
Add @convex-dev/eslint-plugin to package-lock.json so npm ci matches package.json. Keep cloud-agent-start.sh watching Convex with the same Clerk placeholder as install. Drop unused submissions by_user and by_status indexes.
There was a problem hiding this comment.
Stale comment
Risk: high. Left a non-blocking comment and did not approve: Cursor Bugbot completed cleanly on this head, but this multi-event schema and host/apply/board change is above the medium approval threshold. Human review is needed; no reviewers were assigned.
Sent by Cursor Approval Agent: Pull Request Router and Approver
Orphan submissions now attach to a fallback event in bounded batches and skip or merge on (eventId, userId) collisions. New nights interpolate the chosen slot count, reserved slugs suffix instead of throwing, and clipboard failures show a selectable URL. Co-authored-by: smile <smile@rayfernando.com>
There was a problem hiding this comment.
Stale comment
Risk: high. Left a non-blocking comment and did not approve: Cursor Bugbot skipped on this head, and this multi-event schema and host/apply/board change is above the medium approval threshold. Human review is needed; no reviewers were assigned.
Sent by Cursor Approval Agent: Pull Request Router and Approver
When attaching a selected orphan, demote it to shortlisted if the night is already full, and do not copy selected status over a non-selected row for the same user.
| const events = await ctx.db.query("events").order("desc").collect(); | ||
| const featuredId = await getStoredFeaturedEventId(ctx); | ||
|
|
||
| const rows = []; | ||
| for (const event of events) { | ||
| const submissions = await ctx.db | ||
| .query("submissions") | ||
| .withIndex("by_event_status", (q) => q.eq("eventId", event._id)) | ||
| .collect(); | ||
| const counts = { | ||
| submitted: 0, | ||
| shortlisted: 0, | ||
| selected: 0, | ||
| rejected: 0, | ||
| }; | ||
| for (const submission of submissions) { | ||
| counts[submission.status] += 1; | ||
| } |
There was a problem hiding this comment.
🟡 Host page reads every application of every night on each load
Every application row of every night is loaded in full (.collect() inside the per-event loop at convex/events.ts:159-162, plus the unbounded event scan at convex/events.ts:154) just to show per-night counts, so the host page gets slower and eventually fails as history accumulates.
Impact: Once enough nights and applications exist, opening the host pages becomes slow and can hit Convex read limits, blocking triage.
Unbounded per-event scan, and it runs on the triage page too
convex/events.ts:149-182 collects all events, then for each event collects all of its submissions through by_event_status with no bound. convex/_generated/ai/guidelines.md (mandatory reading per AGENTS.md) states: "Never an unbounded .collect() on a table that can grow — use .withIndex(...) and .paginate(...)/.take(n)" and "Never use .collect().length to count rows".
It is not only the desk: components/HostEventBody.tsx:24 calls api.events.listForHost on every /host/[slug] load merely to derive the boolean featured flag (components/HostEventBody.tsx:38-46), which could be read from settings alone.
Prompt for agents
convex/events.ts listForHost collects every event and then every submission of each event to compute status counts, an unbounded N+1 read that violates the repo's mandated Convex guidelines (no unbounded .collect(), no .collect().length counting). It is also fetched by components/HostEventBody.tsx just to learn whether the event is featured. Consider (a) exposing a cheap query that returns only the stored featured event id for the triage page, and (b) bounding or denormalizing the per-event counts (e.g. maintain counters on the event document in submissions.submit/setStatus, or use .take(n) with a cap and label it as such).
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
✅ Resolved: Host counts use bounded .take() in convex/lib/counts.ts (countSelected / countByStatus). events.listForHost does not .collect() the submissions table. Orphan backfill is paginated and gated by submissionEventBackfillDone.
| # auth.config.ts always reads this. A placeholder lets --once push public | ||
| # functions when Clerk is unset. Gated routes still need the real issuer. | ||
| bunx convex env set CLERK_JWT_ISSUER_DOMAIN \ | ||
| "${CLERK_JWT_ISSUER_DOMAIN:-https://unused.clerk.accounts.dev}" |
There was a problem hiding this comment.
🟡 Setup script can overwrite a working sign-in configuration with a placeholder
The Clerk issuer value on the Convex deployment is unconditionally overwritten with a dummy address (convex env set CLERK_JWT_ISSUER_DOMAIN "${CLERK_JWT_ISSUER_DOMAIN:-https://unused.clerk.accounts.dev}" at scripts/cloud-agent-install.sh:35-36) whenever the shell variable happens to be unset, so a deployment that was already configured silently stops accepting sign-ins.
Impact: Re-running the install script in a shell without the Clerk variable can leave everyone signed out with no error message.
Behavior change versus the previous guard
Before this PR the script exited early when CLERK_JWT_ISSUER_DOMAIN was unset (removed lines 27-31), leaving whatever value the deployment already had. Now it always writes, and the fallback is a non-existent issuer. convex env set targets whatever deployment CONVEX_DEPLOYMENT resolves to, which for a developer running the script locally can be a real dev deployment rather than the anonymous local backend. With a wrong issuer, convex/auth.config.ts validation fails and the app is silently always-signed-out.
A safer shape is to only write the placeholder when the deployment has no value yet, or only in anonymous/local mode.
Prompt for agents
scripts/cloud-agent-install.sh now always runs `convex env set CLERK_JWT_ISSUER_DOMAIN` with a placeholder fallback, whereas it previously skipped the Convex push entirely when the variable was unset. If the resolved deployment is a real (cloud dev) deployment that already had the correct Clerk Frontend API URL, the placeholder clobbers it and sign-in silently fails. Gate the placeholder write so it only applies when the deployment has no value yet (or only when running against the anonymous local backend), and keep the real value untouched otherwise.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
✅ Resolved: scripts/cloud-agent-install.sh writes https://placeholder.invalid only when Clerk is unset and the stored issuer is missing or the old unused.clerk placeholder. A real issuer is preserved.
| export const ensureSeed = mutation({ | ||
| args: {}, | ||
| returns: v.union(v.id("events"), v.null()), | ||
| handler: async (ctx) => { | ||
| await requireHost(ctx); | ||
|
|
||
| const event = await resolveSeedEvent(ctx); | ||
| if (event === null) { | ||
| return null; | ||
| } | ||
|
|
||
| if ((await getStoredFeaturedEventId(ctx)) === null) { | ||
| await setFeaturedEvent(ctx, event._id); | ||
| } | ||
|
|
||
| await continueOrphanBackfill(ctx, event._id, null); | ||
| return event._id; | ||
| }, | ||
| }); |
There was a problem hiding this comment.
🟡 Existing lineup and landing page go blank after deploy until a host signs in
An already-running site loses its public running order and landing copy (public pages resolve a night through resolveEvent at convex/lib/eventLookup.ts:47-55, which finds nothing until the host-only ensureSeed at convex/events.ts:347-365 runs) before any night exists, so attendees see an empty board.
Impact: After deploying this change to a live site, the projector board and landing page show "no night" until a host signs in and opens the host page.
Why the recovery path is host-gated and client-triggered
On an existing deployment, events and settings are empty and every submissions row has eventId === undefined. submissions.board (convex/submissions.ts:197-211) and events.bySlug therefore return [] / null, so /board shows the empty state and / renders components/EmptyEvent.tsx.
The only code path that creates the seed event and attaches legacy rows is events.ensureSeed, which calls requireHost and is invoked from a client effect in components/HostDesk.tsx:36-38. Until an allow-listed host signs in and mounts /host, no public page recovers, and submissions.setStatus also refuses the legacy rows (convex/submissions.ts:159-161).
A data migration that does not depend on an authenticated host visit (e.g. a one-shot internal mutation run at deploy time) would remove the window.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
✅ Resolved: events.ensurePublicSeed exists and only writes when the events table is empty. EmptyEvent mounts SeedIfEmpty. Public landing, board, and apply no longer wait for a host to open /host.
There was a problem hiding this comment.
Stale comment
Risk: high. Left a non-blocking comment and did not approve: Cursor Bugbot skipped on this head, and this multi-event schema, data migration, and host/apply/board change is above the medium approval threshold. Human review is needed; no reviewers were assigned.
Sent by Cursor Approval Agent: Pull Request Router and Approver
Public empty pages now run the one-shot seed and orphan backfill so the landing and board come back after deploy without a host login. Orphan merge keeps the higher status and the newer copy, and demotes to shortlisted when the night is full. Capacity edits rewrite the house rules. Install no longer clobbers a real Clerk issuer. Co-authored-by: smile <smile@rayfernando.com>
guestCopy lives on the event. ApplyForm, the board heading, and the house brand read it. HTW labels are bot-demo. SF keeps the old lightning labels. Same submission fields. Co-authored-by: Ray Fernando <RayFernando1337@users.noreply.github.com>
There was a problem hiding this comment.
Stale comment
Risk: high. Left a non-blocking comment and did not approve: Cursor Bugbot skipped on this head and left unresolved findings, and this multi-event schema, data migration, and host/apply/board change is above the medium approval threshold. Human review is needed; no reviewers were assigned.
Sent by Cursor Approval Agent: Pull Request Router and Approver
/harness/guest reads the featured night from Convex and renders the apply labels plus BoardList. Production still shows the setup checklist when Clerk keys are missing. internalSeedDemoTalk puts one selected row on a night so the board is not empty. Co-authored-by: Ray Fernando <RayFernando1337@users.noreply.github.com>
headers() reads incoming request headers, so x-pathname must be
applied via NextResponse.next({ request: { headers } }) for /harness
to skip SetupNotice when Clerk is missing.
There was a problem hiding this comment.
Stale comment
Risk: high. Left a non-blocking comment and did not approve; Cursor Bugbot finished as skipped, and this multi-event schema, auth, and host/apply/board change is above the medium approval threshold. No reviewers were assigned; human review is needed.
Sent by Cursor Approval Agent: Pull Request Router and Approver
ensureSeed no longer re-archives SF or rewrites featuredEventId after HTW staging. Missing aios-sf-lightning is a no-op instead of archiving the first events row. Host triage paginates newest-first so every application stays reachable. Co-authored-by: Ray Fernando <RayFernando1337@users.noreply.github.com>
Co-authored-by: Ray Fernando <RayFernando1337@users.noreply.github.com>
There was a problem hiding this comment.
Stale comment
Risk: high. Left a non-blocking comment and did not approve; Cursor Bugbot completed with no unresolved findings, but this multi-event schema, auth, and host/apply/board change is above the medium approval threshold. No reviewers were assigned; human review is needed.
Sent by Cursor Approval Agent: Pull Request Router and Approver
Guests see Board plus My slot or Sign in. Hosts get one Desk link. Main night, Public, Triage, and the Host chip leave the global bar. Each item is a padded square hit. The glass row does not wrap. Co-authored-by: Ray Fernando <RayFernando1337@users.noreply.github.com>
There was a problem hiding this comment.
Stale comment
Risk: high. Left a non-blocking comment and did not approve; Cursor Bugbot completed with no new findings, but this multi-event schema, auth, and host/apply/board change is above the medium approval threshold. No reviewers were assigned; human review is needed.
Sent by Cursor Approval Agent: Pull Request Router and Approver
There was a problem hiding this comment.
Devin Review found 2 new potential issues.
11 flags not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)
| room: v.string(), | ||
| capacity: v.number(), | ||
| dryRun: v.string(), | ||
| heroImage: v.string(), |
There was a problem hiding this comment.
There was a problem hiding this comment.
✅ Resolved: Dismissed. Hosts cannot set heroImage from the UI. events.create copies SITE.heroImage. events.update has no heroImage argument. Seeds use the house path. No arbitrary URL input, so no extra validator.
|
Header IA proof on Guest 390: Board, My slot, square avatar. One glass row. No Main night in the bar. Host 390: Desk, Board, My slot, square avatar. One glass row. No Host chip. Guest 1280: ticket + event line on the left. Bar is Board, My slot, avatar. Host 1280: same left chrome. Bar is Desk, Board, My slot, avatar. |
resolveEvent treats phase archived as not found, so /e/[slug], apply, and board stay off the guest surface. Hosts still load those nights through hostBySlug and listForHost. Rows stay in the table. Co-authored-by: Ray Fernando <RayFernando1337@users.noreply.github.com>
There was a problem hiding this comment.
Risk: high. Left a non-blocking comment and did not approve; Cursor Bugbot finished as skipped and reported an unresolved finding, and this multi-event schema, auth, and host/apply/board change is above the medium approval threshold. No reviewers were assigned; human review is needed.
Sent by Cursor Approval Agent: Pull Request Router and Approver
Archive only takes a night off / and the public picker. resolveEvent still returns archived events by slug so /e/[slug], apply, and board keep working for printed QRs and host preview.
There was a problem hiding this comment.
Risk: high. Left a non-blocking comment and did not approve; Cursor Bugbot completed successfully with no unresolved findings, but this multi-event schema, auth, and host/apply/board change is above the medium approval threshold. No reviewers were assigned; human review is needed.
Sent by Cursor Approval Agent: Pull Request Router and Approver


LGTM BRO
Note
Medium Risk
Multi-event changes the data model, auth routes, and host/submission rules; misconfigured featured event or
HOST_EMAILScould block hosts or split traffic across nights. The agent skill bulk add is docs-only for runtime.Overview
Multi-event nights replace the single hard-coded night in
lib/content.ts. Convex now ownsevents,settings(featured night), and submissions scoped byeventId, with per-nightcapacityand guest copy. Public routes add/e/[slug], apply, and board;/,/apply, and/boardfollow the featured event./hostbecomes a nights desk and/host/[slug]triages one lineup; Clerk middleware extends to/e/*/apply. Landing, apply, and board pages delegate to shared event-aware components.Agent and styling tooling adds the full
.agents/skills/convex-*skill pack,.cursor/mcp.jsonfor the Convex MCP, Convex AI blocks inAGENTS.md/CLAUDE.md, shadcn/tw-animate tokens inglobals.css, and dev harness pages (guest copy/board, header IA) with a Convex-only layout when Clerk is missing on/harness.Docs describe HTW as the featured night, slug URLs for room QRs, and Convex deploy notes for preview + new functions.
Reviewed by Cursor Bugbot for commit 6b5023f. Bugbot is set up for automated code reviews on this repo. Configure here.