Skip to content

Add world discovery and activity surfaces#86

Merged
BASIC-BIT merged 8 commits into
mainfrom
feat/world-profile-foundation
May 25, 2026
Merged

Add world discovery and activity surfaces#86
BASIC-BIT merged 8 commits into
mainfrom
feat/world-profile-foundation

Conversation

@BASIC-BIT
Copy link
Copy Markdown
Owner

Summary

  • Add first-class world profiles, public /w/[slug] pages, event-world associations, and world-derived event context.
  • Add Home world discovery cards from confirmed event-world data, plus profile/world creator commerce and reciprocal world credits.
  • Add marketplace API research gate and update planning/backend docs for safe storefront boundaries.

Why

This lands the world discovery lane as a coherent PR while keeping early activity labels event-derived and avoiding live VRChat presence, scraped popularity, or marketplace credential work.

Testing

  • pnpm test:backend
  • pnpm typecheck:backend
  • pnpm lint:web
  • pnpm typecheck:web
  • pnpm verify:backend:local
  • pnpm build:web
  • pnpm test:e2e
  • pnpm test:e2e:visual

Risk Notes

  • Draft PR for review; no marketplace API sync, checkout, event CRUD, live presence, or scraping is included.
  • Public projections filter URL-bearing fields to https and omit raw source/association confidence details.

Refs #79
Closes #84
Closes #81
Closes #80
Closes #82
Closes #83

@vercel
Copy link
Copy Markdown

vercel Bot commented May 25, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
vr-dex-web Ready Ready Preview, Comment May 25, 2026 11:32pm

Request Review

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 25, 2026

Playwright Public Screenshot Preview

Outcome: success
Run: https://github.com/BASIC-BIT/VRDex/actions/runs/26423952078
Artifact: playwright-public-preview

Captured routes:

  • /
  • /submit
  • /server-status
  • /deployment
  • /p/playwright-dj-aurora
  • /c/playwright-afterglow-social

This job is required for PR checks; pixel diff baselines are not enabled yet.

@BASIC-BIT BASIC-BIT marked this pull request as ready for review May 25, 2026 04:59
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 203a2d0063

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread convex/_worldPublic.ts Outdated
Comment thread convex/_worldEvents.ts Outdated
@BASIC-BIT
Copy link
Copy Markdown
Owner Author

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ac6f072f48

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread convex/_worldEvents.ts Outdated
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 25, 2026

Greptile Summary

This PR lands world discovery as a coherent feature slice: first-class world profiles with public /w/[slug] pages, event-world association tracking, home page "active worlds" cards derived from confirmed event-world links, and reciprocal world credits on profile pages. All public projections strip internal fields and enforce HTTPS-only URLs at both backend and frontend layers.

  • Adds worlds, events, eventWorlds, and worldProfileCredits Convex tables with slug validation, public projection helpers, and layered tests covering filtering, deduplication, and URL sanitization.
  • Adds the home page HomeActiveWorldsSection and the /w/[slug] world page, both backed by fixture-first server fetchers with correct missing-URL and error fallbacks.
  • The home page active-worlds query (getPublicActiveWorlds) uses an event-centric scan that becomes O(events × associations) at scale; combined with force-dynamic on the home page, this warrants a query strategy review before the event catalog grows.

Confidence Score: 3/5

The PR is draft and the feature logic is sound, but the home page active-worlds query accumulates up to ~2,100 Convex reads on every uncached request — a pattern that needs to be addressed before the event catalog grows or traffic picks up.

The world projection, slug helpers, URL sanitization, and fixture/test coverage are all well-executed. The concern is getPublicActiveWorlds: it scans all published events, then fans out to association and world reads per event, all on the most frequently visited page with no caching. This is a structural issue with the query strategy that sits on the critical render path. The denormalized eventStartAt field also introduces a future data-integrity risk whenever event rescheduling is added.

convex/_worldEvents.ts (getPublicActiveWorlds query fan-out) and convex/schema.ts (eventWorlds table — missing compound index and eventStartAt drift risk).

Important Files Changed

Filename Overview
convex/_worldEvents.ts New module providing world-event context queries; contains an O(events × associations) N+1 pattern in getPublicActiveWorlds that will execute on every uncached home page load.
convex/schema.ts Adds worlds, events, eventWorlds, and worldProfileCredits tables with good index coverage; missing a by_eventId_confirmationState compound index and the denormalized eventStartAt field needs drift-prevention discipline in future mutations.
convex/_worldPublic.ts Clean public projection that strips internal fields and filters all URLs through safeHttpsUrl; correct and well-tested.
apps/web/src/convex/server.ts Adds fetchPublicWorldBySlug and fetchHomeActiveWorlds with correct fixture-first, missing-URL, and error fallback paths.
apps/web/src/app/_components/world-public-page.tsx Large new public world page; all external URLs re-validated client-side, outbound links use rel=noreferrer, image URLs enforce HTTPS.
apps/web/src/app/_components/home-active-worlds.tsx Home page world discovery section; correctly handles empty state and backend status; safeImageBackground sanitizes image URLs.
tests/backend/world-foundation.test.ts Comprehensive unit tests for slug helpers, VRChat ID validation, public projection, and event context filtering including unsafe URL stripping and deduplication edge cases.

Sequence Diagram

sequenceDiagram
    participant Browser
    participant NextJS as Next.js (force-dynamic)
    participant Server as server.ts
    participant Convex as Convex Backend

    Browser->>NextJS: GET /
    NextJS->>Server: fetchHomeActiveWorlds()
    Server->>Convex: worlds.listHomeActiveWorlds
    Convex->>Convex: query events (up to 50 future + 50 past)
    loop For each event (up to 100)
        Convex->>Convex: query eventWorlds by_eventId + filter
        loop For each association (up to 20)
            Convex->>Convex: db.get(worldId)
        end
    end
    Convex-->>NextJS: PublicActiveWorldPreview[]
    NextJS-->>Browser: HomeActiveWorldsSection

    Browser->>NextJS: GET /w/[slug]
    NextJS->>Server: fetchPublicWorldBySlug(slug)
    Server->>Convex: worlds.getPublicBySlug
    Convex->>Convex: getWorldBySlug (by_slug index)
    Convex->>Convex: getPublicWorldEventContext (compound index)
    Convex-->>NextJS: PublicWorld + eventContext
    NextJS-->>Browser: WorldPublicPage
Loading

Comments Outside Diff (3)

  1. convex/_worldEvents.ts, line 390-440 (link)

    P1 N+1 query chain on every uncached home page load

    getPublicActiveWorlds scans up to 50 future + 50 past events, then for each event issues an eventWorlds association query (up to 20 results), and for each association issues a db.get for the world — an O(events × associations) read chain. With force-dynamic on the home page, every visitor triggers this. At 100 events × 20 associations the function makes roughly 2,100 Convex reads before returning 3–6 cards. Convex's 8,192-read-per-query budget leaves little headroom, and the home page has no caching layer above it. Querying from the world side (e.g., indexing worlds that have confirmed upcoming events, or adding a dedicated activeWorlds denormalized collection) would collapse this to a bounded set of reads regardless of total event count.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: convex/_worldEvents.ts
    Line: 390-440
    
    Comment:
    **N+1 query chain on every uncached home page load**
    
    `getPublicActiveWorlds` scans up to 50 future + 50 past events, then for each event issues an `eventWorlds` association query (up to 20 results), and for each association issues a `db.get` for the world — an O(events × associations) read chain. With `force-dynamic` on the home page, every visitor triggers this. At 100 events × 20 associations the function makes roughly 2,100 Convex reads before returning 3–6 cards. Convex's 8,192-read-per-query budget leaves little headroom, and the home page has no caching layer above it. Querying from the world side (e.g., indexing worlds that have confirmed upcoming events, or adding a dedicated `activeWorlds` denormalized collection) would collapse this to a bounded set of reads regardless of total event count.
    
    How can I resolve this? If you propose a fix, please make it concise.
  2. convex/schema.ts, line 954-972 (link)

    P2 Denormalized eventStartAt can drift from events.startAt

    eventWorlds.eventStartAt mirrors the event's startAt and is the range key in by_worldId_confirmationState_eventStartAt. getPublicWorldEventContext relies on this value to bucket associations into upcoming vs. recent. If events.startAt is ever updated (e.g., reschedule), eventWorlds.eventStartAt won't update automatically, so the index query bounds will classify the event in the wrong temporal bin. Since no event-mutation handler exists in this PR, there is nothing enforcing the invariant yet. The risk should be documented and the invariant enforced in any future mutation that changes events.startAt.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: convex/schema.ts
    Line: 954-972
    
    Comment:
    **Denormalized `eventStartAt` can drift from `events.startAt`**
    
    `eventWorlds.eventStartAt` mirrors the event's `startAt` and is the range key in `by_worldId_confirmationState_eventStartAt`. `getPublicWorldEventContext` relies on this value to bucket associations into upcoming vs. recent. If `events.startAt` is ever updated (e.g., reschedule), `eventWorlds.eventStartAt` won't update automatically, so the index query bounds will classify the event in the wrong temporal bin. Since no event-mutation handler exists in this PR, there is nothing enforcing the invariant yet. The risk should be documented and the invariant enforced in any future mutation that changes `events.startAt`.
    
    How can I resolve this? If you propose a fix, please make it concise.
  3. convex/schema.ts, line 964-972 (link)

    P2 Missing compound index for eventId + confirmationState

    getPublicActiveWorlds (in _worldEvents.ts) queries eventWorlds by eventId and then uses a .filter() for confirmationState. Without a by_eventId_confirmationState compound index, Convex must scan all rows matching eventId before filtering — the full association list for each event. Adding .index("by_eventId_confirmationState", ["eventId", "confirmationState"]) would let the index skip unconfirmed rows directly and also support future targeted queries.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: convex/schema.ts
    Line: 964-972
    
    Comment:
    **Missing compound index for `eventId + confirmationState`**
    
    `getPublicActiveWorlds` (in `_worldEvents.ts`) queries `eventWorlds` by `eventId` and then uses a `.filter()` for `confirmationState`. Without a `by_eventId_confirmationState` compound index, Convex must scan all rows matching `eventId` before filtering — the full association list for each event. Adding `.index("by_eventId_confirmationState", ["eventId", "confirmationState"])` would let the index skip unconfirmed rows directly and also support future targeted queries.
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
Fix the following 4 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 4
convex/_worldEvents.ts:390-440
**N+1 query chain on every uncached home page load**

`getPublicActiveWorlds` scans up to 50 future + 50 past events, then for each event issues an `eventWorlds` association query (up to 20 results), and for each association issues a `db.get` for the world — an O(events × associations) read chain. With `force-dynamic` on the home page, every visitor triggers this. At 100 events × 20 associations the function makes roughly 2,100 Convex reads before returning 3–6 cards. Convex's 8,192-read-per-query budget leaves little headroom, and the home page has no caching layer above it. Querying from the world side (e.g., indexing worlds that have confirmed upcoming events, or adding a dedicated `activeWorlds` denormalized collection) would collapse this to a bounded set of reads regardless of total event count.

### Issue 2 of 4
convex/schema.ts:954-972
**Denormalized `eventStartAt` can drift from `events.startAt`**

`eventWorlds.eventStartAt` mirrors the event's `startAt` and is the range key in `by_worldId_confirmationState_eventStartAt`. `getPublicWorldEventContext` relies on this value to bucket associations into upcoming vs. recent. If `events.startAt` is ever updated (e.g., reschedule), `eventWorlds.eventStartAt` won't update automatically, so the index query bounds will classify the event in the wrong temporal bin. Since no event-mutation handler exists in this PR, there is nothing enforcing the invariant yet. The risk should be documented and the invariant enforced in any future mutation that changes `events.startAt`.

### Issue 3 of 4
convex/schema.ts:964-972
**Missing compound index for `eventId + confirmationState`**

`getPublicActiveWorlds` (in `_worldEvents.ts`) queries `eventWorlds` by `eventId` and then uses a `.filter()` for `confirmationState`. Without a `by_eventId_confirmationState` compound index, Convex must scan all rows matching `eventId` before filtering — the full association list for each event. Adding `.index("by_eventId_confirmationState", ["eventId", "confirmationState"])` would let the index skip unconfirmed rows directly and also support future targeted queries.

### Issue 4 of 4
convex/_worldEvents.ts:212-227
**`safeHttpsUrl` and `optionalField` are duplicated across four modules**

Identical implementations of `safeHttpsUrl` appear in `_profilePublic.ts`, `_worldEvents.ts`, and `_worldPublic.ts`; `optionalField` is duplicated in `_profilePublic.ts`, `_profileWorldCredits.ts`, `_worldEvents.ts`, and `_worldPublic.ts`. Extracting both to a shared `_utils.ts` module would make future changes consistent and eliminate the drift risk.

Reviews (1): Last reviewed commit: "fix: dedupe world event previews" | Re-trigger Greptile

Comment thread convex/_worldEvents.ts
@github-advanced-security
Copy link
Copy Markdown

You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool.

What Enabling Code Scanning Means:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

@BASIC-BIT BASIC-BIT merged commit 2780065 into main May 25, 2026
11 checks passed
@BASIC-BIT BASIC-BIT deleted the feat/world-profile-foundation branch May 25, 2026 23:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants