Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
- `AGENTS.md` - repo-wide agent rules and durable workflow defaults
- `AGENTS.local.md.example` - local operator preference template for `AGENTS.local.md`
- `apps/web` - initial `Next.js` web application scaffold
- `convex` - initial Convex backend functions, schema, and generated API types
- `convex` - initial Convex backend functions, base profile schema, and generated API types
- `docs/README.md` - docs entry point
- `docs/planning/` - product, architecture, roadmap, backlog, and issue-planning docs
- `docs/agentic/` - software-factory, onboarding, and agent workflow docs
Expand Down Expand Up @@ -36,6 +36,8 @@ The local Convex bootstrap now mirrors `CONVEX_URL` into `apps/web/.env.local` a

The first server-side `Next.js -> Convex` baseline now lives at `/server-status`. It uses `fetchQuery` from `convex/nextjs` on a dedicated route rendered dynamically, while the homepage keeps the reactive client-side `useQuery` path.

The first product schema table is `profiles`, covering the shared durable record for both people and communities. See `docs/backend/profile-schema.md` for the current field and state contract.

`pnpm verify` is the full repo verification pass and now includes the local Convex bootstrap checks. If you are iterating on the web app only, use `pnpm verify:web` for the lighter web-only path.

## Start here
Expand Down
4 changes: 3 additions & 1 deletion convex/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
This directory holds the initial Convex backend slice for `VRDex`.

- `health.ts` exposes the placeholder public query `health:status`
- `schema.ts` keeps the starting schema explicit and intentionally empty
- `schema.ts` defines the base `profiles` table for people and communities
- `_generated/` contains committed Convex codegen output and should not be edited by hand
- `tsconfig.json` is the Convex-managed TypeScript config for backend functions

Expand All @@ -16,3 +16,5 @@ Use the repo-root scripts for local work:
- `pnpm check:backend:generated`

The canonical workflow notes live in `docs/backend/convex-bootstrap.md`.

The profile schema contract lives in `docs/backend/profile-schema.md`.
50 changes: 48 additions & 2 deletions convex/schema.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,49 @@
import { defineSchema } from "convex/server";
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";

export default defineSchema({});
const profileType = v.union(v.literal("person"), v.literal("community"));

const claimState = v.union(
v.literal("unclaimed"),
v.literal("claimed_unverified"),
v.literal("claimed_verified"),
);

const publicationState = v.union(
v.literal("draft_private"),
v.literal("published"),
);

const creationSource = v.union(
v.literal("self"),
v.literal("community"),
v.literal("concierge"),
v.literal("import"),
v.literal("moderator"),
);

export default defineSchema({
profiles: defineTable({
profileType,
displayName: v.string(),
sortName: v.string(),
aliases: v.array(v.string()),
headline: v.optional(v.string()),
bio: v.optional(v.string()),
region: v.optional(v.string()),
timezone: v.optional(v.string()),
claimState,
publicationState,
creationSource,
// Mutations must set claimedAt/publishedAt with state transitions
// and patch updatedAt on every profile write.
claimedAt: v.optional(v.number()),
publishedAt: v.optional(v.number()),
Comment thread
BASIC-BIT marked this conversation as resolved.
updatedAt: v.number(),
Comment thread
BASIC-BIT marked this conversation as resolved.
})
.index("by_profileType_publicationState", ["profileType", "publicationState"])
.index("by_publicationState_claimState", ["publicationState", "claimState"])
.index("by_claimState_profileType", ["claimState", "profileType"])
.index("by_creationSource_claimState", ["creationSource", "claimState"])
.index("by_profileType_sortName", ["profileType", "sortName"]),
Comment thread
BASIC-BIT marked this conversation as resolved.
});
9 changes: 5 additions & 4 deletions docs/backend/convex-bootstrap.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

## Status note

This doc captures the initial Convex backend slice landed for `#54`.
This doc captures the initial Convex backend slice landed for `#54`, plus the first product schema slice from `#9`.

It is intentionally narrow: enough structure to run Convex locally, generate typed backend helpers, and prove the backend is wired, without prematurely locking product tables or auth decisions.
It is intentionally narrow: enough structure to run Convex locally, generate typed backend helpers, prove the backend is wired, and define the first profile table without prematurely locking auth, permissions, slugs, or rich product flows.

## Locked decision

Expand All @@ -14,7 +14,7 @@ It is intentionally narrow: enough structure to run Convex locally, generate typ

## Current implementation

- `convex/schema.ts` defines an explicit empty schema so later table work starts from a typed baseline instead of ad hoc implicit tables
- `convex/schema.ts` defines the first durable `profiles` table for people and communities
- `convex/health.ts` exposes a minimal public query, `health:status`, that confirms the backend is reachable without hard-coding early product domain records
- `convex.json` pins Convex to Node `22` so local backend runtime expectations stay aligned with the repo's current Node baseline
- `convex/tsconfig.json` provides the TypeScript settings Convex uses to typecheck backend source files
Expand Down Expand Up @@ -54,7 +54,8 @@ Keep the initial backend slice simple:

- `#55` wires the web app to the first Convex client/runtime path using `health:status`
- `#64` adds the first server-side `Next.js -> Convex` data path with `fetchQuery` on `/server-status`
- schema, auth, billing, and production deployment posture should land in their own issues instead of bloating the bootstrap
- profile schema, auth, billing, and production deployment posture should land in their own issues instead of bloating the bootstrap
- `#9` adds the first product table, `profiles`, while keeping slugs, auth/account links, permissions, and type-specific fields deferred

## App Router baseline

Expand Down
87 changes: 87 additions & 0 deletions docs/backend/profile-schema.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Profile Schema

## Status Note

This doc captures the first durable profile schema slice for `#9`.

The schema is intentionally narrow. It establishes one shared `profiles` table for people and communities without introducing slug generation, auth/account links, claim flows, permissions, normalized link tables, asset tables, search-specific indexing, or type-specific profile fields.

## Locked Decisions

- profiles are first-class records independent from the user account that may later claim them
- `profileType` is explicit and currently supports `person` and `community`
- claim state, publication state, and creation provenance are separate fields
- community-submitted unclaimed records are represented by `creationSource: "community"` plus `claimState: "unclaimed"`
- account/user references are deferred until auth and claim issues define the account model
- slugs are deferred to `#10`
- type-aware person/community detail fields are deferred to `#11`
- normalized alias, link, asset, and rich authored block tables are deferred to later profile presentation issues

## `profiles` Table

Core identity fields:

- `profileType`: `"person" | "community"`
- `displayName`: public display name
- `sortName`: normalized display-sort key for deterministic listing
- `aliases`: alternate names or searchable display variants kept inline for the first schema slice

Core presentation fields:

- `headline`: optional short label or one-line positioning statement
- `bio`: optional short public bio
- `region`: optional location or scene region text
- `timezone`: optional time zone text

State fields:

- `claimState`: `"unclaimed" | "claimed_unverified" | "claimed_verified"`
- `publicationState`: `"draft_private" | "published"`
- `creationSource`: `"self" | "community" | "concierge" | "import" | "moderator"`
- `claimedAt`: optional claim timestamp, present only after claim authority is established
- `publishedAt`: optional publication timestamp, present once a profile has been published
- `updatedAt`: application-maintained update timestamp that every profile mutation must refresh

Convex automatically provides `_id` and `_creationTime`; those are not duplicated in the schema.

## State Semantics

`claimState` describes owner authority:

- `unclaimed`: no owner authority has been attached yet
- `claimed_unverified`: a claimant controls the profile, but stronger verification is not complete
- `claimed_verified`: owner control and verification are both established

`publicationState` describes public surfacing:

- `draft_private`: not public and not searchable
- `published`: eligible for public profile pages and later discovery flows, subject to future permission, trust, and opt-out rules

`creationSource` describes how the record entered the system. It is not an authority marker by itself; authority comes from `claimState` and later claim records.

## Mutation Contracts

Convex schema validation cannot enforce conditional timestamp invariants, so profile mutations must preserve these application-level rules:

- set `claimedAt` when `claimState` leaves `"unclaimed"`
- set `publishedAt` when `publicationState` becomes `"published"`
- patch `updatedAt` on every profile write

## Initial Indexes

- `by_profileType_publicationState`: public page/discovery entry points split by person vs community
- `by_publicationState_claimState`: public/trust filtering for later profile lists
- `by_claimState_profileType`: moderation and claim-review flows by claim state, with optional type splitting
- `by_creationSource_claimState`: moderation and community-submitted/unclaimed review flows
- `by_profileType_sortName`: deterministic profile listing by type

## Follow-On Boundaries

- `#10` adds canonical slugs, validation, and uniqueness rules
- `#11` adds type-aware person/community fields and documents shared vs type-specific data
- `#12` defines read/write permission behavior
- `#13` implements claim-state transitions and trust labeling behavior
- `#22` adds presentation assets and owner-authored content sections
- `#23` adds community submission flows and source attribution details
- `#27` adds field-level visibility controls
- `#31` adds public search behavior and any search-specific indexing
6 changes: 6 additions & 0 deletions docs/planning/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ vrdex/

One row per person or community.

Implementation status:

- `#9` establishes one shared Convex `profiles` table
- claim state, publication state, and creation source are split into separate fields
- account/user links, slug generation, type-specific fields, assets, and search-specific indexes are deferred to follow-on issues

Suggested fields:

- `id`
Expand Down
Loading