founder-schema 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -99,6 +99,35 @@ instance; this package must not bring its own.
99
99
 
100
100
  ---
101
101
 
102
+ ## Changes in 0.10.0: the Me hub (2026-10-11)
103
+
104
+ Me becomes a hub: the member's own details, their public profile, and a
105
+ global leaderboard with their own place. No export removed; nothing a 0.9.0
106
+ client parses changes shape.
107
+
108
+ - **Added: the member's personal details**, owner only, on their own route:
109
+ `myPersonalDetailsSchema` (`GET /profiles/me/personal`: name, email, phone,
110
+ gender, date of birth) and `updateMyPersonalDetailsBodySchema`
111
+ (`PATCH /profiles/me/personal`: name, phone, gender). They are on no other
112
+ answer, `founderProfileSchema` included: the app shell reads that on every
113
+ page. The date of birth is read only (the age check's input), the email is
114
+ the sign-in account's.
115
+ - **One rule per field:** `displayNameSchema` (new, in core) is the name rule
116
+ of onboarding's finish, the profile patch and the personal patch, by object
117
+ identity.
118
+ - **Widened request body:** `updateFounderProfileBodySchema.location` and
119
+ `.venture` accept `null`, which removes them (the venture's business type
120
+ with it). Absent still means untouched.
121
+ - **Added, the leaderboard's period and the member's place:**
122
+ `CONTRIBUTION_PERIOD_VALUES` (`recent`, the default, or `all`),
123
+ `contributionLeaderboardQuerySchema` (`?period=`), `contributionYouSchema`,
124
+ and on `contributionLeaderboardSchema` the OPTIONAL `period` and `you`, so a
125
+ 0.10.0 client still parses a 0.9.0 server. Every board is global.
126
+ - **Deploy:** the backend's new `contribution_leaders_all` index needs
127
+ `sync-indexes --apply`.
128
+
129
+ ---
130
+
102
131
  ## Breaking in 0.9.0: communities (2026-10-08)
103
132
 
104
133
  Official communities (domains/communities), a capability system
@@ -273,6 +273,14 @@ export declare const genderSchema: z.ZodEnum<{
273
273
  prefer_not_to_say: "prefer_not_to_say";
274
274
  }>;
275
275
  export type Gender = z.infer<typeof genderSchema>;
276
+ /**
277
+ * The name a member is shown by. ONE rule for every body that writes it:
278
+ * onboarding's finish, the profile patch and the personal-details patch, so a
279
+ * name one accepts the others can never refuse. Plain bounds, no custom
280
+ * message: `too_small` with minimum 1 already says "required", and the client
281
+ * words it.
282
+ */
283
+ export declare const displayNameSchema: z.ZodString;
276
284
  /**
277
285
  * Indian mobile number: ten raw digits, no country code, matching the standard
278
286
  * used across this family. Display adds the `+91`.
@@ -1 +1 @@
1
- {"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/core/common.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/core/common.ts"],"names":[],"mappings":"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"}
@@ -7,7 +7,7 @@
7
7
  * @module founder-schema/core/common
8
8
  */
9
9
  import { z } from 'zod';
10
- import { CITY_MAX, COUNTRY_MAX, LINK_URL_MAX, PAGE_SIZE_DEFAULT, PAGE_SIZE_MAX, STATE_MAX, } from './limits.js';
10
+ import { CITY_MAX, COUNTRY_MAX, DISPLAY_NAME_MAX, LINK_URL_MAX, PAGE_SIZE_DEFAULT, PAGE_SIZE_MAX, STATE_MAX, } from './limits.js';
11
11
  import { VALIDATION_MESSAGE } from './validation.js';
12
12
  // ─── String helpers ──────────────────────────────────────────────────────────
13
13
  /**
@@ -341,6 +341,15 @@ export const goalSchema = z.enum(GOAL_VALUES);
341
341
  */
342
342
  export const GENDER_VALUES = ['male', 'female', 'other', 'prefer_not_to_say'];
343
343
  export const genderSchema = z.enum(GENDER_VALUES);
344
+ // ─── Display name ────────────────────────────────────────────────────────────
345
+ /**
346
+ * The name a member is shown by. ONE rule for every body that writes it:
347
+ * onboarding's finish, the profile patch and the personal-details patch, so a
348
+ * name one accepts the others can never refuse. Plain bounds, no custom
349
+ * message: `too_small` with minimum 1 already says "required", and the client
350
+ * words it.
351
+ */
352
+ export const displayNameSchema = z.string().trim().min(1).max(DISPLAY_NAME_MAX);
344
353
  // ─── Phone ───────────────────────────────────────────────────────────────────
345
354
  /**
346
355
  * Indian mobile number: ten raw digits, no country code, matching the standard
@@ -1 +1 @@
1
- {"version":3,"file":"common.js","sourceRoot":"","sources":["../../src/core/common.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * founder-schema — Shared helpers and core enums\n *\n * The stable building blocks reused across every Founder Circle schema.\n * Anything that both the backend and a client need to agree on lives here.\n *\n * @module founder-schema/core/common\n */\n\nimport { z } from 'zod';\nimport {\n CITY_MAX,\n COUNTRY_MAX,\n LINK_URL_MAX,\n PAGE_SIZE_DEFAULT,\n PAGE_SIZE_MAX,\n STATE_MAX,\n} from './limits.js';\nimport { VALIDATION_MESSAGE } from './validation.js';\n\n// ─── String helpers ──────────────────────────────────────────────────────────\n\n/**\n * Converts empty / whitespace-only strings to `undefined` so Zod `.optional()`\n * treats them as \"not provided\".\n *\n * Why this exists: an HTML form submits `\"\"` for a cleared field, and without\n * this an optional field would be stored as an empty string rather than left\n * alone. Jansathi shipped that bug in dating onboarding.\n */\nexport const emptyStringToUndefined = (value: unknown): unknown => {\n if (typeof value === 'string' && value.trim() === '') return undefined;\n return value;\n};\n\n/** Optional trimmed string. Empty strings are coerced to `undefined`. */\nexport const optionalTrimmedString = (maxLength = 100) =>\n z.preprocess(emptyStringToUndefined, z.string().trim().max(maxLength).optional());\n\n/**\n * A repeated query param (`?skill=growth&skill=sales`) or a single value,\n * always as an array, at most `max` values.\n *\n * Express gives `string` for one occurrence and `string[]` for several, so\n * without this the same URL shape parses two different ways. EMPTY values are\n * dropped (`?industry=` is a select the member just reset): asking for fewer\n * filters is never a 400.\n */\nexport const multiValueQuery = <T extends z.ZodType>(inner: T, max: number) =>\n z.preprocess((value) => {\n if (value === undefined || value === null) return undefined;\n const list = (Array.isArray(value) ? value : [value]).filter((v) => v !== '');\n return list.length > 0 ? list : undefined;\n }, z.array(inner).max(max).optional());\n\n/** A Mongo ObjectId as it crosses the wire: 24 lowercase hex characters. */\nexport const objectIdSchema = z\n .string()\n .regex(/^[0-9a-f]{24}$/i, VALIDATION_MESSAGE.invalidId)\n .transform((v) => v.toLowerCase());\n/** Named for what it is on the wire, so it never collides with Mongoose's `ObjectId`. */\nexport type ObjectIdString = z.infer<typeof objectIdSchema>;\n\n// ─── URLs ────────────────────────────────────────────────────────────────────\n\n/**\n * A URL safe to store and, after the consumer's own check, to render.\n *\n * Use this for EVERY member-supplied URL. Never bare `z.url()`: it accepts any\n * scheme the URL parser accepts, so `javascript:alert(document.cookie)`,\n * `data:text/html,<script>...`, `vbscript:` and `file:///etc/passwd` all pass.\n * Each of those is stored XSS the moment a client renders it as an `href`, and\n * `data:` is the same in an `<img src>`.\n *\n * `hostname` is restricted too, not only the scheme. A bare host like\n * `localhost` or a literal IP is refused, which closes server-side request\n * forgery: this product runs on EC2 with an instance role, so a member-supplied\n * `http://169.254.169.254/latest/meta-data/` that anything server-side ever\n * fetches is a credential read.\n *\n * Validating here is necessary and not sufficient. A consumer still treats a\n * stored URL as untrusted at render time, because rows written before this\n * existed were never checked.\n *\n * ⚠️ IF THIS EVER GROWS A HOST ALLOWLIST, compare the PARSED hostname for\n * exact equality. Never `includes`, never `endsWith`, never a regex over the\n * whole URL. Each of these is a real bypass of a naive \"is it YouTube\" check,\n * and all three read as obviously fine:\n *\n * https://evil.example/?next=youtube.com/watch contains the string\n * youtube.com.evil.example starts with it\n * youtu.be.evil.example a suffix check passes it\n *\n * The same mistake in the opposite direction to the `javascript:` hole: there\n * the scheme was too permissive, here the host check only LOOKS strict.\n * (krish hit this rendering inline video, 2026-09-21, and handles it by parsing\n * and comparing the hostname exactly.)\n */\nexport const safeUrlSchema = z.url({\n protocol: /^https?$/,\n hostname: z.regexes.domain,\n error: VALIDATION_MESSAGE.urlInvalid,\n});\nexport type SafeUrl = z.infer<typeof safeUrlSchema>;\n\n/**\n * A URL a member TYPES: `safeUrlSchema` plus the `LINK_URL_MAX` ceiling, so\n * every member URL carries the same cap and no consumer has to remember it.\n *\n * Two schemas, not one, because the cap is about what a person types and is\n * wrong for what the server reads from elsewhere. Until 0.2.0 there was only\n * this one, and it capped a link preview's CDN image URL too: one 314-character\n * `imageUrl` would have failed a whole feed page.\n */\nexport const httpUrlSchema = safeUrlSchema.max(LINK_URL_MAX);\nexport type HttpUrl = z.infer<typeof httpUrlSchema>;\n\n/**\n * True when `url` parses and its hostname is EXACTLY one of `hosts`\n * (lowercase). The one host check in this package, written the way the warning\n * on `safeUrlSchema` demands: parsed, compared for equality, never matched as\n * text. `youtube.com.evil.example` and `evil.example/?u=youtube.com` are refused.\n */\nexport function isUrlOnHost(url: string, hosts: readonly string[]): boolean {\n try {\n return hosts.includes(new URL(url).hostname.toLowerCase());\n } catch {\n return false;\n }\n}\n\n/** A typed URL (`httpUrlSchema`) that must also live on one of `hosts`. */\nexport const httpUrlOnHosts = (hosts: readonly string[], message: string) =>\n httpUrlSchema.refine((url) => isUrlOnHost(url, hosts), { error: message });\n\n/**\n * What a product needs before it can be listed in Discover (0.6.0). Here, not\n * in the products domain, because an error's details carry it\n * (`PRODUCT_NOT_LISTABLE` -> `details.missing`) and core never imports a domain.\n */\nexport const PRODUCT_LISTING_REQUIREMENT_VALUES = ['logo', 'tagline', 'screenshot'] as const;\nexport const productListingRequirementSchema = z.enum(PRODUCT_LISTING_REQUIREMENT_VALUES);\nexport type ProductListingRequirement = z.infer<typeof productListingRequirementSchema>;\n\n/**\n * The hosts a shared video may come from. The user's ruling (2026-09-21): video\n * is shared as a link and played in the card, never uploaded. The web plays it\n * through youtube-nocookie.com, the one video frame its CSP admits.\n */\nexport const YOUTUBE_HOSTS = [\n 'youtube.com',\n 'www.youtube.com',\n 'm.youtube.com',\n 'youtu.be',\n // The privacy-mode player's own links (0.7.0).\n 'youtube-nocookie.com',\n 'www.youtube-nocookie.com',\n] as const;\n\n/**\n * The 11-character id of a YouTube VIDEO link, or null for anything else (a\n * channel, a playlist, the home page, another site): what a page can embed.\n * Reads `youtu.be/<id>`, `/watch?v=<id>`, and `/shorts/`, `/embed/`, `/live/<id>`\n * on a `YOUTUBE_HOSTS` host (0.7.0).\n */\nexport function youtubeVideoId(url: string): string | null {\n let u: URL;\n try {\n u = new URL(url);\n } catch {\n return null;\n }\n const host = u.hostname.toLowerCase();\n if (!(YOUTUBE_HOSTS as readonly string[]).includes(host)) return null;\n const id =\n host === 'youtu.be'\n ? (u.pathname.split('/')[1] ?? null)\n : u.pathname === '/watch'\n ? u.searchParams.get('v')\n : (/^\\/(?:shorts|embed|live)\\/([^/]+)/.exec(u.pathname)?.[1] ?? null);\n return id && /^[A-Za-z0-9_-]{11}$/.test(id) ? id : null;\n}\n\n// ─── Username ────────────────────────────────────────────────────────────────\n\n/**\n * Public handle. Derived by the server at onboarding, changeable later.\n *\n * Rules are deliberately narrow because the handle appears in URLs and in\n * mentions: lowercase letters, digits, underscore and a single dot, 3 to 30\n * characters, must start with a letter.\n *\n * This is the FORMAT, and it is what every read shape uses. A handle being\n * CHOSEN must also not be reserved: that is `chosenUsernameSchema`.\n */\nexport const USERNAME_MIN = 3;\nexport const USERNAME_MAX = 30;\n\nexport const usernameSchema = z\n .string()\n .trim()\n .toLowerCase()\n // Plain bounds: the client words them from the issue's own minimum/maximum.\n .min(USERNAME_MIN)\n .max(USERNAME_MAX)\n .regex(/^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)?$/, VALIDATION_MESSAGE.usernameInvalid);\nexport type Username = z.infer<typeof usernameSchema>;\n\n/**\n * Handles this product keeps for itself: route names a handle would shadow,\n * and words that would let a member pass as staff.\n *\n * ⚠ HERE, IN THE CONTRACT, since 0.2.0. It used to live in the backend so it\n * could grow without a publish, and nothing ever read it: the server derived\n * handles without checking it, so a member called \"Support\" was handed\n * `support`. One list, read by the server when it derives a handle and by any\n * form that lets a member choose one, is worth a publish per addition.\n *\n * Matched exactly, after lowercasing.\n */\nexport const RESERVED_USERNAMES = [\n 'admin',\n 'about',\n 'api',\n 'chat',\n 'discover',\n 'feed',\n 'founder',\n 'foundercircle',\n 'help',\n 'home',\n 'me',\n 'meetup',\n 'moderator',\n 'onboarding',\n 'settings',\n 'staff',\n 'superadmin',\n 'support',\n 'system',\n 'team',\n] as const;\n\nconst RESERVED = new Set<string>(RESERVED_USERNAMES);\n\nexport function isReservedUsername(handle: string): boolean {\n return RESERVED.has(handle.trim().toLowerCase());\n}\n\n/**\n * A handle a member is CHOOSING: the format, and not reserved.\n *\n * ⚠ Not used on read shapes, deliberately. A handle stored before the reserved\n * check existed must still READ, or one member's old handle would fail every\n * page that shows their name.\n */\nexport const chosenUsernameSchema = usernameSchema.refine((u) => !isReservedUsername(u), {\n error: VALIDATION_MESSAGE.usernameReserved,\n});\n\n// ─── Founder-facing enums ────────────────────────────────────────────────────\n\n/**\n * Stage of the founder's current venture.\n *\n * Ordered from earliest to latest so a range filter can compare by index. Add\n * new stages at the END or the stored ordinal of existing rows shifts.\n */\nexport const STARTUP_STAGE_VALUES = [\n 'idea',\n 'validation',\n 'mvp',\n 'pre_seed',\n 'seed',\n 'series_a_plus',\n 'profitable',\n 'exited',\n] as const;\nexport const startupStageSchema = z.enum(STARTUP_STAGE_VALUES);\nexport type StartupStage = z.infer<typeof startupStageSchema>;\n\n/**\n * Industry the venture operates in. Kept as a closed list so Discover filters\n * are a single indexed equality match rather than a text search.\n */\nexport const INDUSTRY_VALUES = [\n 'agritech',\n 'ai_ml',\n 'b2b_saas',\n 'consumer',\n 'climate',\n 'd2c',\n 'deeptech',\n 'edtech',\n 'fintech',\n 'gaming',\n 'healthtech',\n 'logistics',\n 'manufacturing',\n 'marketplace',\n 'media',\n 'mobility',\n 'proptech',\n 'robotics',\n 'services',\n 'space',\n 'other',\n] as const;\nexport const industrySchema = z.enum(INDUSTRY_VALUES);\nexport type Industry = z.infer<typeof industrySchema>;\n\n/**\n * What the founder brings. Free-text skills were considered and rejected: two\n * people typing \"growth\" and \"Growth Marketing\" would never match each other in\n * a filter, and the Discover tab is the whole point of the product.\n */\nexport const SKILL_VALUES = [\n 'engineering',\n 'product',\n 'design',\n 'data',\n 'growth',\n 'sales',\n 'marketing',\n 'operations',\n 'finance',\n 'legal',\n 'hr_people',\n 'supply_chain',\n 'research',\n 'fundraising',\n 'community',\n] as const;\nexport const skillSchema = z.enum(SKILL_VALUES);\nexport type Skill = z.infer<typeof skillSchema>;\n\n/**\n * What the member is here for. ONE list, replacing three.\n *\n * This was `lookingFor` with six values. The design draws eight \"Goals &\n * Passion\" tiles and four more on the business step; all three were the same\n * idea at three grains, and shipping them together would have meant nobody\n * could say which one Discover filters on - the answer settled by whichever\n * query got written first. Merged on hary's ruling, 2026-09-21, while there\n * were still no rows in any database.\n *\n * ⚠️ Stored per member and used as a Mongoose enum. Append only; removing a\n * value orphans documents already saved with it.\n *\n * ⚠️ `start_company` READS ODDLY next to its neighbours and is correct. It was\n * queried on the grounds that it restates `venture.stage: 'idea'`; it does not,\n * and the reason is worth keeping so nobody re-opens it. Inferring it from the\n * venture works only for a member who ALREADY HAS a venture row, and somebody\n * who wants to start a company is precisely the member with no venture at all,\n * so there is nothing to infer from. It also cannot express a founder with a\n * running company who wants to start another. Every other value is an ask of\n * other people and this one is an ask of the product, which is the whole of the\n * oddness. (hary, 2026-09-21.)\n */\nexport const GOAL_VALUES = [\n 'find_cofounder',\n 'find_investors',\n 'find_customers',\n 'hiring',\n 'partnerships',\n 'mentorship',\n 'grow_network',\n 'business_ideas',\n 'market_insights',\n 'learn_startup',\n 'start_company',\n] as const;\nexport const goalSchema = z.enum(GOAL_VALUES);\nexport type Goal = z.infer<typeof goalSchema>;\n\n// ─── Gender ──────────────────────────────────────────────────────────────────\n\n/**\n * Demographic only. Nothing here computes anything from it, unlike the sibling\n * gym product where gender feeds body-metric maths.\n *\n * Takes the family's three values and ADDS one rather than renaming any: a\n * network for founders should let somebody decline the question without having\n * to describe themselves as \"other\".\n */\nexport const GENDER_VALUES = ['male', 'female', 'other', 'prefer_not_to_say'] as const;\nexport const genderSchema = z.enum(GENDER_VALUES);\nexport type Gender = z.infer<typeof genderSchema>;\n\n// ─── Phone ───────────────────────────────────────────────────────────────────\n\n/**\n * Indian mobile number: ten raw digits, no country code, matching the standard\n * used across this family. Display adds the `+91`.\n *\n * ⚠️ WHERE IT IS STORED IS NOT A SCHEMA QUESTION, and it has been ruled: the\n * phone lives on the account mirror, never as a second copy on the profile.\n * Two stored copies drift the first time somebody edits one, and the profile's\n * would be the copy nobody keeps in sync.\n */\nexport const phoneSchema = z\n .string()\n .trim()\n .regex(/^[6-9]\\d{9}$/, VALIDATION_MESSAGE.phoneInvalid);\nexport type Phone = z.infer<typeof phoneSchema>;\n\n// ─── Location ────────────────────────────────────────────────────────────────\n\n/**\n * Where the founder is based. City and country are what Discover filters on.\n *\n * ONE SHAPE for the profile, the Me tab's patch, onboarding's finish and the\n * location suggestion. It was written out three times until 0.2.0, the same\n * kind of copy that let the profile model lose Instagram.\n *\n * ⚠ `country` is REQUIRED and has NO default. A default here (it used to be\n * `'India'`) is destructive on every write path: a founder in Berlin who\n * corrects only their city would be moved to India by a value they never saw.\n * A body that carries a location carries the whole location. Every stored\n * location has a country, because the model requires one, so the read side\n * loses nothing.\n *\n * This is a DECLARED location, not a GPS reading. Jansathi's rule applies\n * here too: never write a declared location from a GPS capture.\n */\nexport const locationSchema = z.object({\n city: z.string().trim().min(1).max(CITY_MAX),\n state: optionalTrimmedString(STATE_MAX),\n country: z.string().trim().min(1).max(COUNTRY_MAX),\n});\nexport type FounderLocation = z.infer<typeof locationSchema>;\n\n/**\n * `GET /api/v1/location/areas/reverse?lat=&lng=`: plain decimal degrees.\n *\n * A QUERY, so coercion is right here: everything in a URL is a string. An\n * emptied parameter is \"missing\", never 0, because 0,0 is a real place in the\n * Gulf of Guinea and would be looked up rather than refused.\n */\nexport const coordinatesQuerySchema = z.object({\n lat: z.preprocess(emptyStringToUndefined, z.coerce.number().min(-90).max(90)),\n lng: z.preprocess(emptyStringToUndefined, z.coerce.number().min(-180).max(180)),\n});\nexport type CoordinatesQuery = z.infer<typeof coordinatesQuerySchema>;\nexport type CoordinatesQueryInput = z.input<typeof coordinatesQuerySchema>;\n\n/**\n * The city these coordinates look like, for the member to CONFIRM, or `null`\n * when the lookup could not place them confidently (or they are outside India,\n * whose area data is all there is). A suggestion, never a write: only what the\n * member then submits becomes their location.\n */\nexport const suggestedLocationSchema = locationSchema.nullable();\nexport type SuggestedLocation = z.infer<typeof suggestedLocationSchema>;\n\n// ─── Pagination ──────────────────────────────────────────────────────────────\n\n/**\n * Cursor pagination query. Feed and Discover both use it.\n *\n * Cursor, not page number: the community feed changes under the reader, and an\n * offset would repeat or skip posts as new ones land.\n */\nexport const paginationQuerySchema = z.object({\n /**\n * `?limit=` with nothing after it is treated as \"not provided\", not as a\n * validation failure. A client that clears the control sends the empty\n * string, and `z.coerce.number()` turns that into 0, which then fails\n * `.min(1)`: the member sees a 400 for a page they never asked to size.\n */\n limit: z.preprocess(\n emptyStringToUndefined,\n z.coerce.number().int().min(1).max(PAGE_SIZE_MAX).default(PAGE_SIZE_DEFAULT),\n ),\n cursor: z.string().trim().min(1).optional(),\n});\nexport type PaginationQuery = z.infer<typeof paginationQuerySchema>;\n/** What a client may put IN the URL: every field optional, numbers as strings. */\nexport type PaginationQueryInput = z.input<typeof paginationQuerySchema>;\n"]}
1
+ {"version":3,"file":"common.js","sourceRoot":"","sources":["../../src/core/common.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * founder-schema — Shared helpers and core enums\n *\n * The stable building blocks reused across every Founder Circle schema.\n * Anything that both the backend and a client need to agree on lives here.\n *\n * @module founder-schema/core/common\n */\n\nimport { z } from 'zod';\nimport {\n CITY_MAX,\n COUNTRY_MAX,\n DISPLAY_NAME_MAX,\n LINK_URL_MAX,\n PAGE_SIZE_DEFAULT,\n PAGE_SIZE_MAX,\n STATE_MAX,\n} from './limits.js';\nimport { VALIDATION_MESSAGE } from './validation.js';\n\n// ─── String helpers ──────────────────────────────────────────────────────────\n\n/**\n * Converts empty / whitespace-only strings to `undefined` so Zod `.optional()`\n * treats them as \"not provided\".\n *\n * Why this exists: an HTML form submits `\"\"` for a cleared field, and without\n * this an optional field would be stored as an empty string rather than left\n * alone. Jansathi shipped that bug in dating onboarding.\n */\nexport const emptyStringToUndefined = (value: unknown): unknown => {\n if (typeof value === 'string' && value.trim() === '') return undefined;\n return value;\n};\n\n/** Optional trimmed string. Empty strings are coerced to `undefined`. */\nexport const optionalTrimmedString = (maxLength = 100) =>\n z.preprocess(emptyStringToUndefined, z.string().trim().max(maxLength).optional());\n\n/**\n * A repeated query param (`?skill=growth&skill=sales`) or a single value,\n * always as an array, at most `max` values.\n *\n * Express gives `string` for one occurrence and `string[]` for several, so\n * without this the same URL shape parses two different ways. EMPTY values are\n * dropped (`?industry=` is a select the member just reset): asking for fewer\n * filters is never a 400.\n */\nexport const multiValueQuery = <T extends z.ZodType>(inner: T, max: number) =>\n z.preprocess((value) => {\n if (value === undefined || value === null) return undefined;\n const list = (Array.isArray(value) ? value : [value]).filter((v) => v !== '');\n return list.length > 0 ? list : undefined;\n }, z.array(inner).max(max).optional());\n\n/** A Mongo ObjectId as it crosses the wire: 24 lowercase hex characters. */\nexport const objectIdSchema = z\n .string()\n .regex(/^[0-9a-f]{24}$/i, VALIDATION_MESSAGE.invalidId)\n .transform((v) => v.toLowerCase());\n/** Named for what it is on the wire, so it never collides with Mongoose's `ObjectId`. */\nexport type ObjectIdString = z.infer<typeof objectIdSchema>;\n\n// ─── URLs ────────────────────────────────────────────────────────────────────\n\n/**\n * A URL safe to store and, after the consumer's own check, to render.\n *\n * Use this for EVERY member-supplied URL. Never bare `z.url()`: it accepts any\n * scheme the URL parser accepts, so `javascript:alert(document.cookie)`,\n * `data:text/html,<script>...`, `vbscript:` and `file:///etc/passwd` all pass.\n * Each of those is stored XSS the moment a client renders it as an `href`, and\n * `data:` is the same in an `<img src>`.\n *\n * `hostname` is restricted too, not only the scheme. A bare host like\n * `localhost` or a literal IP is refused, which closes server-side request\n * forgery: this product runs on EC2 with an instance role, so a member-supplied\n * `http://169.254.169.254/latest/meta-data/` that anything server-side ever\n * fetches is a credential read.\n *\n * Validating here is necessary and not sufficient. A consumer still treats a\n * stored URL as untrusted at render time, because rows written before this\n * existed were never checked.\n *\n * ⚠️ IF THIS EVER GROWS A HOST ALLOWLIST, compare the PARSED hostname for\n * exact equality. Never `includes`, never `endsWith`, never a regex over the\n * whole URL. Each of these is a real bypass of a naive \"is it YouTube\" check,\n * and all three read as obviously fine:\n *\n * https://evil.example/?next=youtube.com/watch contains the string\n * youtube.com.evil.example starts with it\n * youtu.be.evil.example a suffix check passes it\n *\n * The same mistake in the opposite direction to the `javascript:` hole: there\n * the scheme was too permissive, here the host check only LOOKS strict.\n * (krish hit this rendering inline video, 2026-09-21, and handles it by parsing\n * and comparing the hostname exactly.)\n */\nexport const safeUrlSchema = z.url({\n protocol: /^https?$/,\n hostname: z.regexes.domain,\n error: VALIDATION_MESSAGE.urlInvalid,\n});\nexport type SafeUrl = z.infer<typeof safeUrlSchema>;\n\n/**\n * A URL a member TYPES: `safeUrlSchema` plus the `LINK_URL_MAX` ceiling, so\n * every member URL carries the same cap and no consumer has to remember it.\n *\n * Two schemas, not one, because the cap is about what a person types and is\n * wrong for what the server reads from elsewhere. Until 0.2.0 there was only\n * this one, and it capped a link preview's CDN image URL too: one 314-character\n * `imageUrl` would have failed a whole feed page.\n */\nexport const httpUrlSchema = safeUrlSchema.max(LINK_URL_MAX);\nexport type HttpUrl = z.infer<typeof httpUrlSchema>;\n\n/**\n * True when `url` parses and its hostname is EXACTLY one of `hosts`\n * (lowercase). The one host check in this package, written the way the warning\n * on `safeUrlSchema` demands: parsed, compared for equality, never matched as\n * text. `youtube.com.evil.example` and `evil.example/?u=youtube.com` are refused.\n */\nexport function isUrlOnHost(url: string, hosts: readonly string[]): boolean {\n try {\n return hosts.includes(new URL(url).hostname.toLowerCase());\n } catch {\n return false;\n }\n}\n\n/** A typed URL (`httpUrlSchema`) that must also live on one of `hosts`. */\nexport const httpUrlOnHosts = (hosts: readonly string[], message: string) =>\n httpUrlSchema.refine((url) => isUrlOnHost(url, hosts), { error: message });\n\n/**\n * What a product needs before it can be listed in Discover (0.6.0). Here, not\n * in the products domain, because an error's details carry it\n * (`PRODUCT_NOT_LISTABLE` -> `details.missing`) and core never imports a domain.\n */\nexport const PRODUCT_LISTING_REQUIREMENT_VALUES = ['logo', 'tagline', 'screenshot'] as const;\nexport const productListingRequirementSchema = z.enum(PRODUCT_LISTING_REQUIREMENT_VALUES);\nexport type ProductListingRequirement = z.infer<typeof productListingRequirementSchema>;\n\n/**\n * The hosts a shared video may come from. The user's ruling (2026-09-21): video\n * is shared as a link and played in the card, never uploaded. The web plays it\n * through youtube-nocookie.com, the one video frame its CSP admits.\n */\nexport const YOUTUBE_HOSTS = [\n 'youtube.com',\n 'www.youtube.com',\n 'm.youtube.com',\n 'youtu.be',\n // The privacy-mode player's own links (0.7.0).\n 'youtube-nocookie.com',\n 'www.youtube-nocookie.com',\n] as const;\n\n/**\n * The 11-character id of a YouTube VIDEO link, or null for anything else (a\n * channel, a playlist, the home page, another site): what a page can embed.\n * Reads `youtu.be/<id>`, `/watch?v=<id>`, and `/shorts/`, `/embed/`, `/live/<id>`\n * on a `YOUTUBE_HOSTS` host (0.7.0).\n */\nexport function youtubeVideoId(url: string): string | null {\n let u: URL;\n try {\n u = new URL(url);\n } catch {\n return null;\n }\n const host = u.hostname.toLowerCase();\n if (!(YOUTUBE_HOSTS as readonly string[]).includes(host)) return null;\n const id =\n host === 'youtu.be'\n ? (u.pathname.split('/')[1] ?? null)\n : u.pathname === '/watch'\n ? u.searchParams.get('v')\n : (/^\\/(?:shorts|embed|live)\\/([^/]+)/.exec(u.pathname)?.[1] ?? null);\n return id && /^[A-Za-z0-9_-]{11}$/.test(id) ? id : null;\n}\n\n// ─── Username ────────────────────────────────────────────────────────────────\n\n/**\n * Public handle. Derived by the server at onboarding, changeable later.\n *\n * Rules are deliberately narrow because the handle appears in URLs and in\n * mentions: lowercase letters, digits, underscore and a single dot, 3 to 30\n * characters, must start with a letter.\n *\n * This is the FORMAT, and it is what every read shape uses. A handle being\n * CHOSEN must also not be reserved: that is `chosenUsernameSchema`.\n */\nexport const USERNAME_MIN = 3;\nexport const USERNAME_MAX = 30;\n\nexport const usernameSchema = z\n .string()\n .trim()\n .toLowerCase()\n // Plain bounds: the client words them from the issue's own minimum/maximum.\n .min(USERNAME_MIN)\n .max(USERNAME_MAX)\n .regex(/^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)?$/, VALIDATION_MESSAGE.usernameInvalid);\nexport type Username = z.infer<typeof usernameSchema>;\n\n/**\n * Handles this product keeps for itself: route names a handle would shadow,\n * and words that would let a member pass as staff.\n *\n * ⚠ HERE, IN THE CONTRACT, since 0.2.0. It used to live in the backend so it\n * could grow without a publish, and nothing ever read it: the server derived\n * handles without checking it, so a member called \"Support\" was handed\n * `support`. One list, read by the server when it derives a handle and by any\n * form that lets a member choose one, is worth a publish per addition.\n *\n * Matched exactly, after lowercasing.\n */\nexport const RESERVED_USERNAMES = [\n 'admin',\n 'about',\n 'api',\n 'chat',\n 'discover',\n 'feed',\n 'founder',\n 'foundercircle',\n 'help',\n 'home',\n 'me',\n 'meetup',\n 'moderator',\n 'onboarding',\n 'settings',\n 'staff',\n 'superadmin',\n 'support',\n 'system',\n 'team',\n] as const;\n\nconst RESERVED = new Set<string>(RESERVED_USERNAMES);\n\nexport function isReservedUsername(handle: string): boolean {\n return RESERVED.has(handle.trim().toLowerCase());\n}\n\n/**\n * A handle a member is CHOOSING: the format, and not reserved.\n *\n * ⚠ Not used on read shapes, deliberately. A handle stored before the reserved\n * check existed must still READ, or one member's old handle would fail every\n * page that shows their name.\n */\nexport const chosenUsernameSchema = usernameSchema.refine((u) => !isReservedUsername(u), {\n error: VALIDATION_MESSAGE.usernameReserved,\n});\n\n// ─── Founder-facing enums ────────────────────────────────────────────────────\n\n/**\n * Stage of the founder's current venture.\n *\n * Ordered from earliest to latest so a range filter can compare by index. Add\n * new stages at the END or the stored ordinal of existing rows shifts.\n */\nexport const STARTUP_STAGE_VALUES = [\n 'idea',\n 'validation',\n 'mvp',\n 'pre_seed',\n 'seed',\n 'series_a_plus',\n 'profitable',\n 'exited',\n] as const;\nexport const startupStageSchema = z.enum(STARTUP_STAGE_VALUES);\nexport type StartupStage = z.infer<typeof startupStageSchema>;\n\n/**\n * Industry the venture operates in. Kept as a closed list so Discover filters\n * are a single indexed equality match rather than a text search.\n */\nexport const INDUSTRY_VALUES = [\n 'agritech',\n 'ai_ml',\n 'b2b_saas',\n 'consumer',\n 'climate',\n 'd2c',\n 'deeptech',\n 'edtech',\n 'fintech',\n 'gaming',\n 'healthtech',\n 'logistics',\n 'manufacturing',\n 'marketplace',\n 'media',\n 'mobility',\n 'proptech',\n 'robotics',\n 'services',\n 'space',\n 'other',\n] as const;\nexport const industrySchema = z.enum(INDUSTRY_VALUES);\nexport type Industry = z.infer<typeof industrySchema>;\n\n/**\n * What the founder brings. Free-text skills were considered and rejected: two\n * people typing \"growth\" and \"Growth Marketing\" would never match each other in\n * a filter, and the Discover tab is the whole point of the product.\n */\nexport const SKILL_VALUES = [\n 'engineering',\n 'product',\n 'design',\n 'data',\n 'growth',\n 'sales',\n 'marketing',\n 'operations',\n 'finance',\n 'legal',\n 'hr_people',\n 'supply_chain',\n 'research',\n 'fundraising',\n 'community',\n] as const;\nexport const skillSchema = z.enum(SKILL_VALUES);\nexport type Skill = z.infer<typeof skillSchema>;\n\n/**\n * What the member is here for. ONE list, replacing three.\n *\n * This was `lookingFor` with six values. The design draws eight \"Goals &\n * Passion\" tiles and four more on the business step; all three were the same\n * idea at three grains, and shipping them together would have meant nobody\n * could say which one Discover filters on - the answer settled by whichever\n * query got written first. Merged on hary's ruling, 2026-09-21, while there\n * were still no rows in any database.\n *\n * ⚠️ Stored per member and used as a Mongoose enum. Append only; removing a\n * value orphans documents already saved with it.\n *\n * ⚠️ `start_company` READS ODDLY next to its neighbours and is correct. It was\n * queried on the grounds that it restates `venture.stage: 'idea'`; it does not,\n * and the reason is worth keeping so nobody re-opens it. Inferring it from the\n * venture works only for a member who ALREADY HAS a venture row, and somebody\n * who wants to start a company is precisely the member with no venture at all,\n * so there is nothing to infer from. It also cannot express a founder with a\n * running company who wants to start another. Every other value is an ask of\n * other people and this one is an ask of the product, which is the whole of the\n * oddness. (hary, 2026-09-21.)\n */\nexport const GOAL_VALUES = [\n 'find_cofounder',\n 'find_investors',\n 'find_customers',\n 'hiring',\n 'partnerships',\n 'mentorship',\n 'grow_network',\n 'business_ideas',\n 'market_insights',\n 'learn_startup',\n 'start_company',\n] as const;\nexport const goalSchema = z.enum(GOAL_VALUES);\nexport type Goal = z.infer<typeof goalSchema>;\n\n// ─── Gender ──────────────────────────────────────────────────────────────────\n\n/**\n * Demographic only. Nothing here computes anything from it, unlike the sibling\n * gym product where gender feeds body-metric maths.\n *\n * Takes the family's three values and ADDS one rather than renaming any: a\n * network for founders should let somebody decline the question without having\n * to describe themselves as \"other\".\n */\nexport const GENDER_VALUES = ['male', 'female', 'other', 'prefer_not_to_say'] as const;\nexport const genderSchema = z.enum(GENDER_VALUES);\nexport type Gender = z.infer<typeof genderSchema>;\n\n// ─── Display name ────────────────────────────────────────────────────────────\n\n/**\n * The name a member is shown by. ONE rule for every body that writes it:\n * onboarding's finish, the profile patch and the personal-details patch, so a\n * name one accepts the others can never refuse. Plain bounds, no custom\n * message: `too_small` with minimum 1 already says \"required\", and the client\n * words it.\n */\nexport const displayNameSchema = z.string().trim().min(1).max(DISPLAY_NAME_MAX);\n\n// ─── Phone ───────────────────────────────────────────────────────────────────\n\n/**\n * Indian mobile number: ten raw digits, no country code, matching the standard\n * used across this family. Display adds the `+91`.\n *\n * ⚠️ WHERE IT IS STORED IS NOT A SCHEMA QUESTION, and it has been ruled: the\n * phone lives on the account mirror, never as a second copy on the profile.\n * Two stored copies drift the first time somebody edits one, and the profile's\n * would be the copy nobody keeps in sync.\n */\nexport const phoneSchema = z\n .string()\n .trim()\n .regex(/^[6-9]\\d{9}$/, VALIDATION_MESSAGE.phoneInvalid);\nexport type Phone = z.infer<typeof phoneSchema>;\n\n// ─── Location ────────────────────────────────────────────────────────────────\n\n/**\n * Where the founder is based. City and country are what Discover filters on.\n *\n * ONE SHAPE for the profile, the Me tab's patch, onboarding's finish and the\n * location suggestion. It was written out three times until 0.2.0, the same\n * kind of copy that let the profile model lose Instagram.\n *\n * ⚠ `country` is REQUIRED and has NO default. A default here (it used to be\n * `'India'`) is destructive on every write path: a founder in Berlin who\n * corrects only their city would be moved to India by a value they never saw.\n * A body that carries a location carries the whole location. Every stored\n * location has a country, because the model requires one, so the read side\n * loses nothing.\n *\n * This is a DECLARED location, not a GPS reading. Jansathi's rule applies\n * here too: never write a declared location from a GPS capture.\n */\nexport const locationSchema = z.object({\n city: z.string().trim().min(1).max(CITY_MAX),\n state: optionalTrimmedString(STATE_MAX),\n country: z.string().trim().min(1).max(COUNTRY_MAX),\n});\nexport type FounderLocation = z.infer<typeof locationSchema>;\n\n/**\n * `GET /api/v1/location/areas/reverse?lat=&lng=`: plain decimal degrees.\n *\n * A QUERY, so coercion is right here: everything in a URL is a string. An\n * emptied parameter is \"missing\", never 0, because 0,0 is a real place in the\n * Gulf of Guinea and would be looked up rather than refused.\n */\nexport const coordinatesQuerySchema = z.object({\n lat: z.preprocess(emptyStringToUndefined, z.coerce.number().min(-90).max(90)),\n lng: z.preprocess(emptyStringToUndefined, z.coerce.number().min(-180).max(180)),\n});\nexport type CoordinatesQuery = z.infer<typeof coordinatesQuerySchema>;\nexport type CoordinatesQueryInput = z.input<typeof coordinatesQuerySchema>;\n\n/**\n * The city these coordinates look like, for the member to CONFIRM, or `null`\n * when the lookup could not place them confidently (or they are outside India,\n * whose area data is all there is). A suggestion, never a write: only what the\n * member then submits becomes their location.\n */\nexport const suggestedLocationSchema = locationSchema.nullable();\nexport type SuggestedLocation = z.infer<typeof suggestedLocationSchema>;\n\n// ─── Pagination ──────────────────────────────────────────────────────────────\n\n/**\n * Cursor pagination query. Feed and Discover both use it.\n *\n * Cursor, not page number: the community feed changes under the reader, and an\n * offset would repeat or skip posts as new ones land.\n */\nexport const paginationQuerySchema = z.object({\n /**\n * `?limit=` with nothing after it is treated as \"not provided\", not as a\n * validation failure. A client that clears the control sends the empty\n * string, and `z.coerce.number()` turns that into 0, which then fails\n * `.min(1)`: the member sees a 400 for a page they never asked to size.\n */\n limit: z.preprocess(\n emptyStringToUndefined,\n z.coerce.number().int().min(1).max(PAGE_SIZE_MAX).default(PAGE_SIZE_DEFAULT),\n ),\n cursor: z.string().trim().min(1).optional(),\n});\nexport type PaginationQuery = z.infer<typeof paginationQuerySchema>;\n/** What a client may put IN the URL: every field optional, numbers as strings. */\nexport type PaginationQueryInput = z.input<typeof paginationQuerySchema>;\n"]}
@@ -27,7 +27,8 @@
27
27
  *
28
28
  * Routes (all signed in):
29
29
  * GET /api/v1/contributions/me paginationQuerySchema -> contributionPageSchema
30
- * GET /api/v1/contributions/leaderboard -> contributionLeaderboardSchema
30
+ * GET /api/v1/contributions/leaderboard contributionLeaderboardQuerySchema
31
+ * -> contributionLeaderboardSchema
31
32
  *
32
33
  * The score itself is on the profile and the card (`contributionSummarySchema`).
33
34
  *
@@ -130,7 +131,27 @@ export declare const contributionPageSchema: z.ZodObject<{
130
131
  nextCursor: z.ZodNullable<z.ZodString>;
131
132
  }, z.core.$strip>;
132
133
  export type ContributionPage = z.infer<typeof contributionPageSchema>;
133
- /** One row of the top helpers of the last `CONTRIBUTION_RECENT_DAYS` days. */
134
+ /**
135
+ * Which points a board ranks by (0.10.0): `recent`, the last
136
+ * `CONTRIBUTION_RECENT_DAYS` days (the default, so a newer member can still
137
+ * climb), or `all`, every point ever earned. Every board is GLOBAL: one list
138
+ * for the whole circle (the user's ruling, 2026-10-11).
139
+ */
140
+ export declare const CONTRIBUTION_PERIOD_VALUES: readonly ["recent", "all"];
141
+ export declare const contributionPeriodSchema: z.ZodEnum<{
142
+ all: "all";
143
+ recent: "recent";
144
+ }>;
145
+ export type ContributionPeriod = z.infer<typeof contributionPeriodSchema>;
146
+ /** `GET /api/v1/contributions/leaderboard?period=` (0.10.0). A query string, so the default is the server's. */
147
+ export declare const contributionLeaderboardQuerySchema: z.ZodObject<{
148
+ period: z.ZodDefault<z.ZodEnum<{
149
+ all: "all";
150
+ recent: "recent";
151
+ }>>;
152
+ }, z.core.$strip>;
153
+ export type ContributionLeaderboardQuery = z.infer<typeof contributionLeaderboardQuerySchema>;
154
+ /** One row of a board: its rank, who, and their points in the period. */
134
155
  export declare const contributionLeaderEntrySchema: z.ZodObject<{
135
156
  rank: z.ZodNumber;
136
157
  member: z.ZodObject<{
@@ -144,10 +165,34 @@ export declare const contributionLeaderEntrySchema: z.ZodObject<{
144
165
  }, z.core.$strip>;
145
166
  export type ContributionLeaderEntry = z.infer<typeof contributionLeaderEntrySchema>;
146
167
  /**
147
- * Top helpers, best first, at most `CONTRIBUTION_LEADERBOARD_SIZE`. Recomputed
148
- * hourly; `computedAt` says when (null before the first run).
168
+ * The signed-in member's own place on the board (0.10.0), on its first page or
169
+ * not. Ranked in the SAME order, among the SAME members, as the list, so their
170
+ * rank is always their position. `rank` is null while they are not on the
171
+ * board: no points in the period (a board ranks help given, and nobody is
172
+ * 40th for nothing), or a profile no longer listed. `points` are theirs either
173
+ * way.
174
+ */
175
+ export declare const contributionYouSchema: z.ZodObject<{
176
+ rank: z.ZodNullable<z.ZodNumber>;
177
+ points: z.ZodNumber;
178
+ }, z.core.$strip>;
179
+ export type ContributionYou = z.infer<typeof contributionYouSchema>;
180
+ /**
181
+ * Top helpers, best first, at most `CONTRIBUTION_LEADERBOARD_SIZE`. Read from
182
+ * the live counters, which move the moment help is recognised; only points
183
+ * leaving the `recent` window wait for the hourly recount. `computedAt` is the
184
+ * latest recount among the rows shown (null before the first run, and for an
185
+ * empty board).
186
+ *
187
+ * `period` and `you` (0.10.0) are OPTIONAL on read, so this client keeps
188
+ * parsing a server from before them: without `you` the board simply shows no
189
+ * row of the member's own.
149
190
  */
150
191
  export declare const contributionLeaderboardSchema: z.ZodObject<{
192
+ period: z.ZodOptional<z.ZodEnum<{
193
+ all: "all";
194
+ recent: "recent";
195
+ }>>;
151
196
  items: z.ZodArray<z.ZodObject<{
152
197
  rank: z.ZodNumber;
153
198
  member: z.ZodObject<{
@@ -159,6 +204,10 @@ export declare const contributionLeaderboardSchema: z.ZodObject<{
159
204
  }, z.core.$strip>;
160
205
  points: z.ZodNumber;
161
206
  }, z.core.$strip>>;
207
+ you: z.ZodOptional<z.ZodObject<{
208
+ rank: z.ZodNullable<z.ZodNumber>;
209
+ points: z.ZodNumber;
210
+ }, z.core.$strip>>;
162
211
  computedAt: z.ZodNullable<z.ZodISODateTime>;
163
212
  }, z.core.$strip>;
164
213
  export type ContributionLeaderboard = z.infer<typeof contributionLeaderboardSchema>;
@@ -1 +1 @@
1
- {"version":3,"file":"contributions.d.ts","sourceRoot":"","sources":["../../../src/domains/contributions/contributions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAOxB,4CAA4C;AAC5C,eAAO,MAAM,wBAAwB,uFAK3B,CAAC;AACX,eAAO,MAAM,sBAAsB;;;;;EAAmC,CAAC;AACvE;;;GAGG;AACH,eAAO,MAAM,gCAAgC,gEAG5C,CAAC;AACF,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE;;;GAGG;AACH,eAAO,MAAM,mBAAmB;;;;;CAKqB,CAAC;AAEtD,4CAA4C;AAC5C,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAoBlC,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAA2C,CAAC;AAC/E,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,8EAA8E;AAC9E,eAAO,MAAM,6BAA6B;;;;;;;;;;iBAKxC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;AAEpF;;;GAGG;AACH,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;iBAGxC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC"}
1
+ {"version":3,"file":"contributions.d.ts","sourceRoot":"","sources":["../../../src/domains/contributions/contributions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAOxB,4CAA4C;AAC5C,eAAO,MAAM,wBAAwB,uFAK3B,CAAC;AACX,eAAO,MAAM,sBAAsB;;;;;EAAmC,CAAC;AACvE;;;GAGG;AACH,eAAO,MAAM,gCAAgC,gEAG5C,CAAC;AACF,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE;;;GAGG;AACH,eAAO,MAAM,mBAAmB;;;;;CAKqB,CAAC;AAEtD,4CAA4C;AAC5C,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAoBlC,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAA2C,CAAC;AAC/E,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,4BAA6B,CAAC;AACrE,eAAO,MAAM,wBAAwB;;;EAAqC,CAAC;AAC3E,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,gHAAgH;AAChH,eAAO,MAAM,kCAAkC;;;;;iBAE7C,CAAC;AACH,MAAM,MAAM,4BAA4B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kCAAkC,CAAC,CAAC;AAE9F,yEAAyE;AACzE,eAAO,MAAM,6BAA6B;;;;;;;;;;iBAKxC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;AAEpF;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB;;;iBAGhC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE;;;;;;;;;;GAUG;AACH,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;;iBAKxC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC"}
@@ -27,7 +27,8 @@
27
27
  *
28
28
  * Routes (all signed in):
29
29
  * GET /api/v1/contributions/me paginationQuerySchema -> contributionPageSchema
30
- * GET /api/v1/contributions/leaderboard -> contributionLeaderboardSchema
30
+ * GET /api/v1/contributions/leaderboard contributionLeaderboardQuerySchema
31
+ * -> contributionLeaderboardSchema
31
32
  *
32
33
  * The score itself is on the profile and the card (`contributionSummarySchema`).
33
34
  *
@@ -85,19 +86,52 @@ export const contributionEntrySchema = z.object({
85
86
  createdAt: z.iso.datetime(),
86
87
  });
87
88
  export const contributionPageSchema = paginatedSchema(contributionEntrySchema);
88
- /** One row of the top helpers of the last `CONTRIBUTION_RECENT_DAYS` days. */
89
+ /**
90
+ * Which points a board ranks by (0.10.0): `recent`, the last
91
+ * `CONTRIBUTION_RECENT_DAYS` days (the default, so a newer member can still
92
+ * climb), or `all`, every point ever earned. Every board is GLOBAL: one list
93
+ * for the whole circle (the user's ruling, 2026-10-11).
94
+ */
95
+ export const CONTRIBUTION_PERIOD_VALUES = ['recent', 'all'];
96
+ export const contributionPeriodSchema = z.enum(CONTRIBUTION_PERIOD_VALUES);
97
+ /** `GET /api/v1/contributions/leaderboard?period=` (0.10.0). A query string, so the default is the server's. */
98
+ export const contributionLeaderboardQuerySchema = z.object({
99
+ period: contributionPeriodSchema.default('recent'),
100
+ });
101
+ /** One row of a board: its rank, who, and their points in the period. */
89
102
  export const contributionLeaderEntrySchema = z.object({
90
103
  rank: z.number().int().positive(),
91
104
  member: memberBylineSchema,
92
- /** Points earned in the window. */
105
+ /** Points earned in the period. */
93
106
  points: z.number().int().positive(),
94
107
  });
95
108
  /**
96
- * Top helpers, best first, at most `CONTRIBUTION_LEADERBOARD_SIZE`. Recomputed
97
- * hourly; `computedAt` says when (null before the first run).
109
+ * The signed-in member's own place on the board (0.10.0), on its first page or
110
+ * not. Ranked in the SAME order, among the SAME members, as the list, so their
111
+ * rank is always their position. `rank` is null while they are not on the
112
+ * board: no points in the period (a board ranks help given, and nobody is
113
+ * 40th for nothing), or a profile no longer listed. `points` are theirs either
114
+ * way.
115
+ */
116
+ export const contributionYouSchema = z.object({
117
+ rank: z.number().int().positive().nullable(),
118
+ points: z.number().int().nonnegative(),
119
+ });
120
+ /**
121
+ * Top helpers, best first, at most `CONTRIBUTION_LEADERBOARD_SIZE`. Read from
122
+ * the live counters, which move the moment help is recognised; only points
123
+ * leaving the `recent` window wait for the hourly recount. `computedAt` is the
124
+ * latest recount among the rows shown (null before the first run, and for an
125
+ * empty board).
126
+ *
127
+ * `period` and `you` (0.10.0) are OPTIONAL on read, so this client keeps
128
+ * parsing a server from before them: without `you` the board simply shows no
129
+ * row of the member's own.
98
130
  */
99
131
  export const contributionLeaderboardSchema = z.object({
132
+ period: contributionPeriodSchema.optional(),
100
133
  items: z.array(contributionLeaderEntrySchema),
134
+ you: contributionYouSchema.optional(),
101
135
  computedAt: z.iso.datetime().nullable(),
102
136
  });
103
137
  //# sourceMappingURL=contributions.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"contributions.js","sourceRoot":"","sources":["../../../src/domains/contributions/contributions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAEnE,4CAA4C;AAC5C,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACtC,kBAAkB;IAClB,kBAAkB;IAClB,gBAAgB;IAChB,gBAAgB;CACR,CAAC;AACX,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;AACvE;;;GAGG;AACH,MAAM,CAAC,MAAM,gCAAgC,GAAG,wBAAwB,CAAC,MAAM,CAC7E,CAAC,CAAC,EAA6E,EAAE,CAC/E,CAAC,KAAK,gBAAgB,CACzB,CAAC;AAGF;;;GAGG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,gBAAgB,EAAE,EAAE;IACpB,gBAAgB,EAAE,EAAE;IACpB,cAAc,EAAE,CAAC;IACjB,cAAc,EAAE,CAAC;CACkC,CAAC;AAEtD,4CAA4C;AAC5C,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C,EAAE,EAAE,cAAc;IAClB,IAAI,EAAE,sBAAsB;IAC5B,gFAAgF;IAChF,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IACnC;;;OAGG;IACH,KAAK,EAAE,kBAAkB,CAAC,QAAQ,EAAE;IACpC,iDAAiD;IACjD,OAAO,EAAE,iBAAiB,CAAC,QAAQ,EAAE;IACrC;;;OAGG;IACH,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE,EAAE,EAAE,cAAc,EAAE,CAAC,CAAC,QAAQ,EAAE;IAC7E,SAAS,EAAE,cAAc,CAAC,QAAQ,EAAE;IACpC,QAAQ,EAAE,cAAc,CAAC,QAAQ,EAAE;IACnC,SAAS,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE;CAC5B,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,sBAAsB,GAAG,eAAe,CAAC,uBAAuB,CAAC,CAAC;AAG/E,8EAA8E;AAC9E,MAAM,CAAC,MAAM,6BAA6B,GAAG,CAAC,CAAC,MAAM,CAAC;IACpD,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IACjC,MAAM,EAAE,kBAAkB;IAC1B,mCAAmC;IACnC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;CACpC,CAAC,CAAC;AAGH;;;GAGG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAG,CAAC,CAAC,MAAM,CAAC;IACpD,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,6BAA6B,CAAC;IAC7C,UAAU,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;CACxC,CAAC,CAAC","sourcesContent":["/**\n * founder-schema — Contribution: the help a member gives, recognised\n *\n * The score counts HELP GIVEN, and only what OTHER members recognise:\n *\n * feedback_helpful a product's team marks your comment \"this helped\" +10\n * tester_confirmed a product's team confirms you tested their product +15\n * review_helpful another member finds your review helpful +2\n * (at most `REVIEW_HELPFUL_POINTS_MAX` per review)\n *\n * `review_written` is still listed so older rows and clients parse, but it is\n * NEVER awarded (0.7.0): writing a review is your own act, and the score\n * counts only what others recognise. A review earns through Helpful votes.\n *\n * Nothing a member does for their OWN product counts, and nothing their own\n * product receives, so the score cannot be bought with a popular product.\n *\n * Against farming: one member can earn at most `CONTRIBUTION_PAIR_POINTS_MAX`\n * points from any ONE other member over 30 days, so two founders cannot trade\n * Helpful marks into a high score.\n *\n * EVERY POINT IS A ROW (the ledger), so a score can be explained (\"how did I\n * earn this?\"), checked and re-tuned. A point stands while what earned it\n * stands: unmarking, withdrawing a vote, deleting the review or comment, or\n * staff removing it takes the point back. An hourly job recounts every score\n * from the ledger, so a total can never drift for long.\n *\n * Routes (all signed in):\n * GET /api/v1/contributions/me paginationQuerySchema -> contributionPageSchema\n * GET /api/v1/contributions/leaderboard -> contributionLeaderboardSchema\n *\n * The score itself is on the profile and the card (`contributionSummarySchema`).\n *\n * @module founder-schema/domains/contributions\n */\n\nimport { z } from 'zod';\nimport { objectIdSchema } from '../../core/common.js';\nimport { paginatedSchema } from '../../core/responses.js';\nimport { contentKindSchema } from '../feed/feed.js';\nimport { productChipSchema } from '../products/products.js';\nimport { memberBylineSchema } from '../profile/founder-profile.js';\n\n/** ⚠ Stored per ledger row: append only. */\nexport const CONTRIBUTION_KIND_VALUES = [\n 'feedback_helpful',\n 'tester_confirmed',\n 'review_helpful',\n 'review_written',\n] as const;\nexport const contributionKindSchema = z.enum(CONTRIBUTION_KIND_VALUES);\n/**\n * The kinds that are actually awarded (0.7.0): every kind but `review_written`,\n * which is never awarded any more. \"How points are earned\" lists these.\n */\nexport const CONTRIBUTION_AWARDED_KIND_VALUES = CONTRIBUTION_KIND_VALUES.filter(\n (k): k is Exclude<(typeof CONTRIBUTION_KIND_VALUES)[number], 'review_written'> =>\n k !== 'review_written',\n);\nexport type ContributionKind = z.infer<typeof contributionKindSchema>;\n\n/**\n * Points per kind. Exhaustive by type. Changing a number changes FUTURE rows;\n * the hourly recount sums what each row was actually awarded.\n */\nexport const CONTRIBUTION_POINTS = {\n feedback_helpful: 10,\n tester_confirmed: 15,\n review_helpful: 2,\n review_written: 3,\n} as const satisfies Record<ContributionKind, number>;\n\n/** One line of \"how I earned my points\". */\nexport const contributionEntrySchema = z.object({\n id: objectIdSchema,\n kind: contributionKindSchema,\n /** What it was actually awarded: below the kind's points when a cap applied. */\n points: z.number().int().positive(),\n /**\n * Who recognised it; null for a deleted account, and for `review_helpful`\n * (a Helpful vote is anonymous: the review shows only a count).\n */\n actor: memberBylineSchema.nullable(),\n /** The product it was about, while it exists. */\n product: productChipSchema.optional(),\n /**\n * What earned it, so the row opens it: the post or poll and the comment for\n * `feedback_helpful`, the review for `review_helpful`.\n */\n content: z.object({ kind: contentKindSchema, id: objectIdSchema }).optional(),\n commentId: objectIdSchema.optional(),\n reviewId: objectIdSchema.optional(),\n createdAt: z.iso.datetime(),\n});\nexport type ContributionEntry = z.infer<typeof contributionEntrySchema>;\n\nexport const contributionPageSchema = paginatedSchema(contributionEntrySchema);\nexport type ContributionPage = z.infer<typeof contributionPageSchema>;\n\n/** One row of the top helpers of the last `CONTRIBUTION_RECENT_DAYS` days. */\nexport const contributionLeaderEntrySchema = z.object({\n rank: z.number().int().positive(),\n member: memberBylineSchema,\n /** Points earned in the window. */\n points: z.number().int().positive(),\n});\nexport type ContributionLeaderEntry = z.infer<typeof contributionLeaderEntrySchema>;\n\n/**\n * Top helpers, best first, at most `CONTRIBUTION_LEADERBOARD_SIZE`. Recomputed\n * hourly; `computedAt` says when (null before the first run).\n */\nexport const contributionLeaderboardSchema = z.object({\n items: z.array(contributionLeaderEntrySchema),\n computedAt: z.iso.datetime().nullable(),\n});\nexport type ContributionLeaderboard = z.infer<typeof contributionLeaderboardSchema>;\n"]}
1
+ {"version":3,"file":"contributions.js","sourceRoot":"","sources":["../../../src/domains/contributions/contributions.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * founder-schema — Contribution: the help a member gives, recognised\n *\n * The score counts HELP GIVEN, and only what OTHER members recognise:\n *\n * feedback_helpful a product's team marks your comment \"this helped\" +10\n * tester_confirmed a product's team confirms you tested their product +15\n * review_helpful another member finds your review helpful +2\n * (at most `REVIEW_HELPFUL_POINTS_MAX` per review)\n *\n * `review_written` is still listed so older rows and clients parse, but it is\n * NEVER awarded (0.7.0): writing a review is your own act, and the score\n * counts only what others recognise. A review earns through Helpful votes.\n *\n * Nothing a member does for their OWN product counts, and nothing their own\n * product receives, so the score cannot be bought with a popular product.\n *\n * Against farming: one member can earn at most `CONTRIBUTION_PAIR_POINTS_MAX`\n * points from any ONE other member over 30 days, so two founders cannot trade\n * Helpful marks into a high score.\n *\n * EVERY POINT IS A ROW (the ledger), so a score can be explained (\"how did I\n * earn this?\"), checked and re-tuned. A point stands while what earned it\n * stands: unmarking, withdrawing a vote, deleting the review or comment, or\n * staff removing it takes the point back. An hourly job recounts every score\n * from the ledger, so a total can never drift for long.\n *\n * Routes (all signed in):\n * GET /api/v1/contributions/me paginationQuerySchema -> contributionPageSchema\n * GET /api/v1/contributions/leaderboard contributionLeaderboardQuerySchema\n * -> contributionLeaderboardSchema\n *\n * The score itself is on the profile and the card (`contributionSummarySchema`).\n *\n * @module founder-schema/domains/contributions\n */\n\nimport { z } from 'zod';\nimport { objectIdSchema } from '../../core/common.js';\nimport { paginatedSchema } from '../../core/responses.js';\nimport { contentKindSchema } from '../feed/feed.js';\nimport { productChipSchema } from '../products/products.js';\nimport { memberBylineSchema } from '../profile/founder-profile.js';\n\n/** ⚠ Stored per ledger row: append only. */\nexport const CONTRIBUTION_KIND_VALUES = [\n 'feedback_helpful',\n 'tester_confirmed',\n 'review_helpful',\n 'review_written',\n] as const;\nexport const contributionKindSchema = z.enum(CONTRIBUTION_KIND_VALUES);\n/**\n * The kinds that are actually awarded (0.7.0): every kind but `review_written`,\n * which is never awarded any more. \"How points are earned\" lists these.\n */\nexport const CONTRIBUTION_AWARDED_KIND_VALUES = CONTRIBUTION_KIND_VALUES.filter(\n (k): k is Exclude<(typeof CONTRIBUTION_KIND_VALUES)[number], 'review_written'> =>\n k !== 'review_written',\n);\nexport type ContributionKind = z.infer<typeof contributionKindSchema>;\n\n/**\n * Points per kind. Exhaustive by type. Changing a number changes FUTURE rows;\n * the hourly recount sums what each row was actually awarded.\n */\nexport const CONTRIBUTION_POINTS = {\n feedback_helpful: 10,\n tester_confirmed: 15,\n review_helpful: 2,\n review_written: 3,\n} as const satisfies Record<ContributionKind, number>;\n\n/** One line of \"how I earned my points\". */\nexport const contributionEntrySchema = z.object({\n id: objectIdSchema,\n kind: contributionKindSchema,\n /** What it was actually awarded: below the kind's points when a cap applied. */\n points: z.number().int().positive(),\n /**\n * Who recognised it; null for a deleted account, and for `review_helpful`\n * (a Helpful vote is anonymous: the review shows only a count).\n */\n actor: memberBylineSchema.nullable(),\n /** The product it was about, while it exists. */\n product: productChipSchema.optional(),\n /**\n * What earned it, so the row opens it: the post or poll and the comment for\n * `feedback_helpful`, the review for `review_helpful`.\n */\n content: z.object({ kind: contentKindSchema, id: objectIdSchema }).optional(),\n commentId: objectIdSchema.optional(),\n reviewId: objectIdSchema.optional(),\n createdAt: z.iso.datetime(),\n});\nexport type ContributionEntry = z.infer<typeof contributionEntrySchema>;\n\nexport const contributionPageSchema = paginatedSchema(contributionEntrySchema);\nexport type ContributionPage = z.infer<typeof contributionPageSchema>;\n\n/**\n * Which points a board ranks by (0.10.0): `recent`, the last\n * `CONTRIBUTION_RECENT_DAYS` days (the default, so a newer member can still\n * climb), or `all`, every point ever earned. Every board is GLOBAL: one list\n * for the whole circle (the user's ruling, 2026-10-11).\n */\nexport const CONTRIBUTION_PERIOD_VALUES = ['recent', 'all'] as const;\nexport const contributionPeriodSchema = z.enum(CONTRIBUTION_PERIOD_VALUES);\nexport type ContributionPeriod = z.infer<typeof contributionPeriodSchema>;\n\n/** `GET /api/v1/contributions/leaderboard?period=` (0.10.0). A query string, so the default is the server's. */\nexport const contributionLeaderboardQuerySchema = z.object({\n period: contributionPeriodSchema.default('recent'),\n});\nexport type ContributionLeaderboardQuery = z.infer<typeof contributionLeaderboardQuerySchema>;\n\n/** One row of a board: its rank, who, and their points in the period. */\nexport const contributionLeaderEntrySchema = z.object({\n rank: z.number().int().positive(),\n member: memberBylineSchema,\n /** Points earned in the period. */\n points: z.number().int().positive(),\n});\nexport type ContributionLeaderEntry = z.infer<typeof contributionLeaderEntrySchema>;\n\n/**\n * The signed-in member's own place on the board (0.10.0), on its first page or\n * not. Ranked in the SAME order, among the SAME members, as the list, so their\n * rank is always their position. `rank` is null while they are not on the\n * board: no points in the period (a board ranks help given, and nobody is\n * 40th for nothing), or a profile no longer listed. `points` are theirs either\n * way.\n */\nexport const contributionYouSchema = z.object({\n rank: z.number().int().positive().nullable(),\n points: z.number().int().nonnegative(),\n});\nexport type ContributionYou = z.infer<typeof contributionYouSchema>;\n\n/**\n * Top helpers, best first, at most `CONTRIBUTION_LEADERBOARD_SIZE`. Read from\n * the live counters, which move the moment help is recognised; only points\n * leaving the `recent` window wait for the hourly recount. `computedAt` is the\n * latest recount among the rows shown (null before the first run, and for an\n * empty board).\n *\n * `period` and `you` (0.10.0) are OPTIONAL on read, so this client keeps\n * parsing a server from before them: without `you` the board simply shows no\n * row of the member's own.\n */\nexport const contributionLeaderboardSchema = z.object({\n period: contributionPeriodSchema.optional(),\n items: z.array(contributionLeaderEntrySchema),\n you: contributionYouSchema.optional(),\n computedAt: z.iso.datetime().nullable(),\n});\nexport type ContributionLeaderboard = z.infer<typeof contributionLeaderboardSchema>;\n"]}
@@ -892,15 +892,15 @@ export declare const onboardingDraftKeySchema: z.ZodEnum<{
892
892
  skills: "skills";
893
893
  goals: "goals";
894
894
  links: "links";
895
- interests: "interests";
895
+ phone: "phone";
896
896
  gender: "gender";
897
897
  dateOfBirth: "dateOfBirth";
898
+ interests: "interests";
898
899
  avatarMediaId: "avatarMediaId";
899
- phone: "phone";
900
900
  businessType: "businessType";
901
901
  guidelinesVersion: "guidelinesVersion";
902
902
  }>;
903
- export declare const ONBOARDING_DRAFT_KEYS: ("location" | "about" | "displayName" | "headline" | "venture" | "skills" | "goals" | "links" | "interests" | "gender" | "dateOfBirth" | "avatarMediaId" | "phone" | "businessType" | "guidelinesVersion")[];
903
+ export declare const ONBOARDING_DRAFT_KEYS: ("location" | "about" | "displayName" | "headline" | "venture" | "skills" | "goals" | "links" | "phone" | "gender" | "dateOfBirth" | "interests" | "avatarMediaId" | "businessType" | "guidelinesVersion")[];
904
904
  export type OnboardingDraftKey = z.infer<typeof onboardingDraftKeySchema>;
905
905
  /**
906
906
  * Where the member got to, kept in its own collection, separate from the
@@ -926,11 +926,11 @@ export declare const onboardingDraftSchema: z.ZodRecord<z.ZodEnum<{
926
926
  skills: "skills";
927
927
  goals: "goals";
928
928
  links: "links";
929
- interests: "interests";
929
+ phone: "phone";
930
930
  gender: "gender";
931
931
  dateOfBirth: "dateOfBirth";
932
+ interests: "interests";
932
933
  avatarMediaId: "avatarMediaId";
933
- phone: "phone";
934
934
  businessType: "businessType";
935
935
  guidelinesVersion: "guidelinesVersion";
936
936
  }> & z.core.$partial, z.ZodUnknown>;
@@ -946,11 +946,11 @@ export declare const onboardingProgressSchema: z.ZodObject<{
946
946
  skills: "skills";
947
947
  goals: "goals";
948
948
  links: "links";
949
- interests: "interests";
949
+ phone: "phone";
950
950
  gender: "gender";
951
951
  dateOfBirth: "dateOfBirth";
952
+ interests: "interests";
952
953
  avatarMediaId: "avatarMediaId";
953
- phone: "phone";
954
954
  businessType: "businessType";
955
955
  guidelinesVersion: "guidelinesVersion";
956
956
  }> & z.core.$partial, z.ZodUnknown>>;
@@ -972,11 +972,11 @@ export declare const onboardingProgressUpdateSchema: z.ZodObject<{
972
972
  skills: "skills";
973
973
  goals: "goals";
974
974
  links: "links";
975
- interests: "interests";
975
+ phone: "phone";
976
976
  gender: "gender";
977
977
  dateOfBirth: "dateOfBirth";
978
+ interests: "interests";
978
979
  avatarMediaId: "avatarMediaId";
979
- phone: "phone";
980
980
  businessType: "businessType";
981
981
  guidelinesVersion: "guidelinesVersion";
982
982
  }> & z.core.$partial, z.ZodUnknown>>;
@@ -1002,11 +1002,11 @@ export declare const onboardingStatusSchema: z.ZodObject<{
1002
1002
  skills: "skills";
1003
1003
  goals: "goals";
1004
1004
  links: "links";
1005
- interests: "interests";
1005
+ phone: "phone";
1006
1006
  gender: "gender";
1007
1007
  dateOfBirth: "dateOfBirth";
1008
+ interests: "interests";
1008
1009
  avatarMediaId: "avatarMediaId";
1009
- phone: "phone";
1010
1010
  businessType: "businessType";
1011
1011
  guidelinesVersion: "guidelinesVersion";
1012
1012
  }> & z.core.$partial, z.ZodUnknown>>;
@@ -1 +1 @@
1
- {"version":3,"file":"onboarding.d.ts","sourceRoot":"","sources":["../../../src/domains/onboarding/onboarding.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"onboarding.d.ts","sourceRoot":"","sources":["../../../src/domains/onboarding/onboarding.ts"],"names":[],"mappings":"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"}
@@ -26,7 +26,7 @@
26
26
  * @module founder-schema/domains/onboarding
27
27
  */
28
28
  import { z } from 'zod';
29
- import { GENDER_VALUES, GOAL_VALUES, genderSchema, goalSchema, INDUSTRY_VALUES, industrySchema, locationSchema, objectIdSchema, optionalTrimmedString, phoneSchema, SKILL_VALUES, STARTUP_STAGE_VALUES, skillSchema, startupStageSchema, } from '../../core/common.js';
29
+ import { displayNameSchema, GENDER_VALUES, GOAL_VALUES, genderSchema, goalSchema, INDUSTRY_VALUES, industrySchema, locationSchema, objectIdSchema, optionalTrimmedString, phoneSchema, SKILL_VALUES, STARTUP_STAGE_VALUES, skillSchema, startupStageSchema, } from '../../core/common.js';
30
30
  import { ABOUT_MAX, CITY_MAX, COUNTRY_MAX, DISPLAY_NAME_MAX, GOALS_MIN, HEADLINE_MAX, LINK_URL_MAX, SKILLS_MAX, STATE_MAX, TEAM_SIZE_MAX, VENTURE_NAME_MAX, } from '../../core/limits.js';
31
31
  import { VALIDATION_MESSAGE } from '../../core/validation.js';
32
32
  import { founderLinksSchema, ventureSchema } from '../profile/founder-profile.js';
@@ -558,7 +558,7 @@ export function readOnboardingDraft(draft) {
558
558
  export const completeOnboardingBodySchema = z.object({
559
559
  // Plain bounds, no custom message: `too_small` with minimum 1 already says
560
560
  // "required", and the client words it in the member's language.
561
- displayName: z.string().trim().min(1).max(DISPLAY_NAME_MAX),
561
+ displayName: displayNameSchema,
562
562
  /**
563
563
  * ⚠️ NO `username` HERE, and its absence is deliberate.
564
564
  *