diff --git a/README.md b/README.md index 0b6a57e..40d218a 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 diff --git a/convex/README.md b/convex/README.md index b1c830e..77f0ae8 100644 --- a/convex/README.md +++ b/convex/README.md @@ -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 @@ -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`. diff --git a/convex/schema.ts b/convex/schema.ts index 956f989..dc8801a 100644 --- a/convex/schema.ts +++ b/convex/schema.ts @@ -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()), + updatedAt: v.number(), + }) + .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"]), +}); diff --git a/docs/backend/convex-bootstrap.md b/docs/backend/convex-bootstrap.md index 4e67fb8..66a3c50 100644 --- a/docs/backend/convex-bootstrap.md +++ b/docs/backend/convex-bootstrap.md @@ -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 @@ -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 @@ -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 diff --git a/docs/backend/profile-schema.md b/docs/backend/profile-schema.md new file mode 100644 index 0000000..11769b2 --- /dev/null +++ b/docs/backend/profile-schema.md @@ -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 diff --git a/docs/planning/architecture.md b/docs/planning/architecture.md index a06f009..9563925 100644 --- a/docs/planning/architecture.md +++ b/docs/planning/architecture.md @@ -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`