founder-schema 0.5.0 → 0.7.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.
Files changed (77) hide show
  1. package/README.md +67 -0
  2. package/dist/access/permissions.d.ts +24 -2
  3. package/dist/access/permissions.d.ts.map +1 -1
  4. package/dist/access/permissions.js +9 -0
  5. package/dist/access/permissions.js.map +1 -1
  6. package/dist/core/common.d.ts +44 -0
  7. package/dist/core/common.d.ts.map +1 -1
  8. package/dist/core/common.js +76 -0
  9. package/dist/core/common.js.map +1 -1
  10. package/dist/core/errors.d.ts +29 -1
  11. package/dist/core/errors.d.ts.map +1 -1
  12. package/dist/core/errors.js +52 -1
  13. package/dist/core/errors.js.map +1 -1
  14. package/dist/core/limits.d.ts +53 -0
  15. package/dist/core/limits.d.ts.map +1 -1
  16. package/dist/core/limits.js +57 -0
  17. package/dist/core/limits.js.map +1 -1
  18. package/dist/core/responses.d.ts +43 -0
  19. package/dist/core/responses.d.ts.map +1 -1
  20. package/dist/core/responses.js +3 -1
  21. package/dist/core/responses.js.map +1 -1
  22. package/dist/core/validation.d.ts +14 -0
  23. package/dist/core/validation.d.ts.map +1 -1
  24. package/dist/core/validation.js +14 -0
  25. package/dist/core/validation.js.map +1 -1
  26. package/dist/domains/audit/audit.d.ts +4 -0
  27. package/dist/domains/audit/audit.d.ts.map +1 -1
  28. package/dist/domains/audit/audit.js +6 -3
  29. package/dist/domains/audit/audit.js.map +1 -1
  30. package/dist/domains/contributions/contributions.d.ts +165 -0
  31. package/dist/domains/contributions/contributions.d.ts.map +1 -0
  32. package/dist/domains/contributions/contributions.js +103 -0
  33. package/dist/domains/contributions/contributions.js.map +1 -0
  34. package/dist/domains/discover/discover.d.ts +5 -0
  35. package/dist/domains/discover/discover.d.ts.map +1 -1
  36. package/dist/domains/discover/discover.js +3 -13
  37. package/dist/domains/discover/discover.js.map +1 -1
  38. package/dist/domains/feed/feed.d.ts +231 -7
  39. package/dist/domains/feed/feed.d.ts.map +1 -1
  40. package/dist/domains/feed/feed.js +86 -18
  41. package/dist/domains/feed/feed.js.map +1 -1
  42. package/dist/domains/media/media.d.ts +30 -5
  43. package/dist/domains/media/media.d.ts.map +1 -1
  44. package/dist/domains/media/media.js +32 -6
  45. package/dist/domains/media/media.js.map +1 -1
  46. package/dist/domains/members/members.d.ts +5 -5
  47. package/dist/domains/moderation/moderation.d.ts +53 -3
  48. package/dist/domains/moderation/moderation.d.ts.map +1 -1
  49. package/dist/domains/moderation/moderation.js +47 -8
  50. package/dist/domains/moderation/moderation.js.map +1 -1
  51. package/dist/domains/notifications/notifications.d.ts +212 -0
  52. package/dist/domains/notifications/notifications.d.ts.map +1 -0
  53. package/dist/domains/notifications/notifications.js +148 -0
  54. package/dist/domains/notifications/notifications.js.map +1 -0
  55. package/dist/domains/onboarding/onboarding.d.ts +10 -13
  56. package/dist/domains/onboarding/onboarding.d.ts.map +1 -1
  57. package/dist/domains/onboarding/onboarding.js +6 -5
  58. package/dist/domains/onboarding/onboarding.js.map +1 -1
  59. package/dist/domains/products/products.d.ts +1464 -0
  60. package/dist/domains/products/products.d.ts.map +1 -0
  61. package/dist/domains/products/products.js +474 -0
  62. package/dist/domains/products/products.js.map +1 -0
  63. package/dist/domains/profile/founder-profile.d.ts +43 -0
  64. package/dist/domains/profile/founder-profile.d.ts.map +1 -1
  65. package/dist/domains/profile/founder-profile.js +48 -0
  66. package/dist/domains/profile/founder-profile.js.map +1 -1
  67. package/dist/domains/reviews/reviews.d.ts +184 -0
  68. package/dist/domains/reviews/reviews.d.ts.map +1 -0
  69. package/dist/domains/reviews/reviews.js +99 -0
  70. package/dist/domains/reviews/reviews.js.map +1 -0
  71. package/dist/domains/uploads/uploads.d.ts +2 -0
  72. package/dist/domains/uploads/uploads.d.ts.map +1 -1
  73. package/dist/index.d.ts +10 -6
  74. package/dist/index.d.ts.map +1 -1
  75. package/dist/index.js +14 -6
  76. package/dist/index.js.map +1 -1
  77. package/package.json +1 -1
@@ -11,7 +11,14 @@
11
11
  * what its author deleted) leaves the reports open and writes nothing: for
12
12
  * those, dismiss.
13
13
  * POST /api/v1/moderation/:contentKind/:contentId/decision { action, note? }
14
- * decide on one post, poll or comment; answers the item as it now is
14
+ * decide on one post, poll, comment, product or review; answers the
15
+ * item as it now is
16
+ *
17
+ * PRODUCTS AND REVIEWS (0.6.0) are reported and decided here too. A product is
18
+ * hidden or restored, never removed by staff (it is its team's work; hiding
19
+ * takes it out of Discover and off every page but its team's). A review is
20
+ * hidden, restored or removed like a comment. The hidden and removed lists
21
+ * hold them too, so a hidden product can be found and put back.
15
22
  *
16
23
  * ONE ITEM PER THING, NOT PER REPORT. Five members reporting one post is one
17
24
  * row with five reports, so a moderator decides once. The queue's order is
@@ -35,20 +42,47 @@ import { z } from 'zod';
35
42
  import { emptyStringToUndefined, objectIdSchema, optionalTrimmedString, paginationQuerySchema, } from '../../core/common.js';
36
43
  import { MODERATION_NOTE_MAX } from '../../core/limits.js';
37
44
  import { paginatedSchema } from '../../core/responses.js';
38
- import { contentKindSchema, engagementTargetKindSchema, reportReasonSchema } from '../feed/feed.js';
39
- /** Where a post, poll or comment stands. */
45
+ import { reportReasonSchema, reportTargetKindSchema } from '../feed/feed.js';
46
+ /**
47
+ * Where something is read in the app: a post or poll, or a product page (for a
48
+ * product, and for a review of it).
49
+ */
50
+ export const MODERATION_CONTEXT_KIND_VALUES = ['post', 'poll', 'product'];
51
+ export const moderationContextKindSchema = z.enum(MODERATION_CONTEXT_KIND_VALUES);
52
+ /** Where a post, poll, comment, product or review stands. */
40
53
  export const MODERATION_STATE_VALUES = ['visible', 'hidden', 'removed'];
41
54
  export const moderationStateSchema = z.enum(MODERATION_STATE_VALUES);
42
55
  export const MODERATION_ACTION_VALUES = ['dismiss', 'hide', 'restore', 'remove'];
43
56
  export const moderationActionSchema = z.enum(MODERATION_ACTION_VALUES);
57
+ /**
58
+ * Which decisions apply to which kind of thing: everything, except that a
59
+ * product is never removed by staff (it is its team's work; hiding it takes it
60
+ * out of Discover and off every page but its team's). The decision dialog
61
+ * offers these, and the server refuses anything else with
62
+ * `MODERATION_ACTION_INVALID`. Exhaustive by type.
63
+ */
64
+ export const MODERATION_ACTIONS_BY_TARGET_KIND = {
65
+ post: MODERATION_ACTION_VALUES,
66
+ poll: MODERATION_ACTION_VALUES,
67
+ comment: MODERATION_ACTION_VALUES,
68
+ review: MODERATION_ACTION_VALUES,
69
+ product: ['dismiss', 'hide', 'restore'],
70
+ };
44
71
  const count = z.number().int().min(0);
45
- /** One post, poll or comment as moderation sees it. */
72
+ /** One post, poll, comment, product or review as moderation sees it. */
46
73
  export const moderationItemSchema = z.object({
47
- target: z.object({ kind: engagementTargetKindSchema, id: objectIdSchema }),
48
- /** The post or poll itself, or the one a comment is on: where it is read in the app. */
49
- content: z.object({ kind: contentKindSchema, id: objectIdSchema }),
50
- /** Its first words (a poll's question), cut to `MODERATION_EXCERPT_MAX`. */
74
+ target: z.object({ kind: reportTargetKindSchema, id: objectIdSchema }),
75
+ /**
76
+ * Where it is read in the app: the post or poll itself, or the one a comment
77
+ * is on; the product page for a product or a review.
78
+ */
79
+ content: z.object({ kind: moderationContextKindSchema, id: objectIdSchema }),
80
+ /**
81
+ * Its first words, cut to `MODERATION_EXCERPT_MAX`: a poll's question, a
82
+ * product's name and tagline, a review's stars and text.
83
+ */
51
84
  excerpt: z.string(),
85
+ /** Who wrote it; for a product, its owner. */
52
86
  author: z.object({
53
87
  id: objectIdSchema,
54
88
  displayName: z.string(),
@@ -81,6 +115,11 @@ export const moderationPageSchema = paginatedSchema(moderationItemSchema);
81
115
  export const moderationContentQuerySchema = paginationQuerySchema.extend({
82
116
  state: z.preprocess(emptyStringToUndefined, moderationStateSchema.default('visible')),
83
117
  });
118
+ /** `POST /moderation/:contentKind/:contentId/decision`: what is decided on. */
119
+ export const moderationTargetParamsSchema = z.object({
120
+ contentKind: reportTargetKindSchema,
121
+ contentId: objectIdSchema,
122
+ });
84
123
  /** `POST /moderation/:contentKind/:contentId/decision`. */
85
124
  export const moderationDecisionBodySchema = z.object({
86
125
  action: moderationActionSchema,
@@ -1 +1 @@
1
- {"version":3,"file":"moderation.js","sourceRoot":"","sources":["../../../src/domains/moderation/moderation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * founder-schema: moderation\n *\n * GET /api/v1/moderation/queue ?limit&cursor\n * what members reported and nobody has decided on, oldest report first\n * GET /api/v1/moderation/content ?state&limit&cursor\n * by state, newest first: visible is posts and polls by when posted;\n * hidden and removed also hold comments (so a hidden comment can be\n * found and put back), by when hidden or removed\n * A decision that changes nothing (hiding what is already hidden, removing\n * what its author deleted) leaves the reports open and writes nothing: for\n * those, dismiss.\n * POST /api/v1/moderation/:contentKind/:contentId/decision { action, note? }\n * decide on one post, poll or comment; answers the item as it now is\n *\n * ONE ITEM PER THING, NOT PER REPORT. Five members reporting one post is one\n * row with five reports, so a moderator decides once. The queue's order is\n * the oldest open report first: whoever has waited longest is seen first, and\n * a pile-on cannot jump the line.\n *\n * THE DECISIONS\n * dismiss it stays up; its open reports close as dismissed\n * hide out of every feed and thread, kept, restorable; reports actioned\n * restore a hidden item goes back up\n * remove deleted as its author deleting it would (counts move), not\n * restorable from here; reports actioned\n * Every decision on someone else's content is written to the audit log, with\n * the note when there is one.\n *\n * A report's `note` was written by a member: untrusted text, shown as text.\n *\n * @module founder-schema/domains/moderation\n */\n\nimport { z } from 'zod';\nimport {\n emptyStringToUndefined,\n objectIdSchema,\n optionalTrimmedString,\n paginationQuerySchema,\n} from '../../core/common.js';\nimport { MODERATION_NOTE_MAX } from '../../core/limits.js';\nimport { paginatedSchema } from '../../core/responses.js';\nimport { contentKindSchema, engagementTargetKindSchema, reportReasonSchema } from '../feed/feed.js';\n\n/** Where a post, poll or comment stands. */\nexport const MODERATION_STATE_VALUES = ['visible', 'hidden', 'removed'] as const;\nexport const moderationStateSchema = z.enum(MODERATION_STATE_VALUES);\nexport type ModerationState = z.infer<typeof moderationStateSchema>;\n\nexport const MODERATION_ACTION_VALUES = ['dismiss', 'hide', 'restore', 'remove'] as const;\nexport const moderationActionSchema = z.enum(MODERATION_ACTION_VALUES);\nexport type ModerationAction = z.infer<typeof moderationActionSchema>;\n\nconst count = z.number().int().min(0);\n\n/** One post, poll or comment as moderation sees it. */\nexport const moderationItemSchema = z.object({\n target: z.object({ kind: engagementTargetKindSchema, id: objectIdSchema }),\n /** The post or poll itself, or the one a comment is on: where it is read in the app. */\n content: z.object({ kind: contentKindSchema, id: objectIdSchema }),\n /** Its first words (a poll's question), cut to `MODERATION_EXCERPT_MAX`. */\n excerpt: z.string(),\n author: z.object({\n id: objectIdSchema,\n displayName: z.string(),\n avatarUrl: z.url().nullable(),\n }),\n createdAt: z.iso.datetime(),\n state: moderationStateSchema,\n reports: z.object({\n /** Reports nobody has decided on. */\n open: count,\n /** Every report ever filed about it. */\n total: count,\n /** Why it was reported, among the open ones, most-given first. */\n reasons: z.array(z.object({ reason: reportReasonSchema, count })),\n /** The reporters' notes, among the open ones, newest first (a few, not all). */\n notes: z.array(z.string()),\n /** When the oldest open report was filed. Null when none is open. */\n oldestOpenAt: z.iso.datetime().nullable(),\n }),\n /** What the asking staff member may do to it now. */\n viewer: z.object({\n canDismiss: z.boolean(),\n canHide: z.boolean(),\n canRestore: z.boolean(),\n canRemove: z.boolean(),\n }),\n});\nexport type ModerationItem = z.infer<typeof moderationItemSchema>;\n\nexport const moderationPageSchema = paginatedSchema(moderationItemSchema);\nexport type ModerationPage = z.infer<typeof moderationPageSchema>;\n\n/** `GET /moderation/content`. Visible when not said. */\nexport const moderationContentQuerySchema = paginationQuerySchema.extend({\n state: z.preprocess(emptyStringToUndefined, moderationStateSchema.default('visible')),\n});\nexport type ModerationContentQuery = z.infer<typeof moderationContentQuerySchema>;\nexport type ModerationContentQueryInput = z.input<typeof moderationContentQuerySchema>;\n\n/** `POST /moderation/:contentKind/:contentId/decision`. */\nexport const moderationDecisionBodySchema = z.object({\n action: moderationActionSchema,\n /** Why, for the audit log. Optional: an obvious spam removal needs no essay. */\n note: optionalTrimmedString(MODERATION_NOTE_MAX),\n});\nexport type ModerationDecisionBody = z.infer<typeof moderationDecisionBodySchema>;\n"]}
1
+ {"version":3,"file":"moderation.js","sourceRoot":"","sources":["../../../src/domains/moderation/moderation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * founder-schema: moderation\n *\n * GET /api/v1/moderation/queue ?limit&cursor\n * what members reported and nobody has decided on, oldest report first\n * GET /api/v1/moderation/content ?state&limit&cursor\n * by state, newest first: visible is posts and polls by when posted;\n * hidden and removed also hold comments (so a hidden comment can be\n * found and put back), by when hidden or removed\n * A decision that changes nothing (hiding what is already hidden, removing\n * what its author deleted) leaves the reports open and writes nothing: for\n * those, dismiss.\n * POST /api/v1/moderation/:contentKind/:contentId/decision { action, note? }\n * decide on one post, poll, comment, product or review; answers the\n * item as it now is\n *\n * PRODUCTS AND REVIEWS (0.6.0) are reported and decided here too. A product is\n * hidden or restored, never removed by staff (it is its team's work; hiding\n * takes it out of Discover and off every page but its team's). A review is\n * hidden, restored or removed like a comment. The hidden and removed lists\n * hold them too, so a hidden product can be found and put back.\n *\n * ONE ITEM PER THING, NOT PER REPORT. Five members reporting one post is one\n * row with five reports, so a moderator decides once. The queue's order is\n * the oldest open report first: whoever has waited longest is seen first, and\n * a pile-on cannot jump the line.\n *\n * THE DECISIONS\n * dismiss it stays up; its open reports close as dismissed\n * hide out of every feed and thread, kept, restorable; reports actioned\n * restore a hidden item goes back up\n * remove deleted as its author deleting it would (counts move), not\n * restorable from here; reports actioned\n * Every decision on someone else's content is written to the audit log, with\n * the note when there is one.\n *\n * A report's `note` was written by a member: untrusted text, shown as text.\n *\n * @module founder-schema/domains/moderation\n */\n\nimport { z } from 'zod';\nimport {\n emptyStringToUndefined,\n objectIdSchema,\n optionalTrimmedString,\n paginationQuerySchema,\n} from '../../core/common.js';\nimport { MODERATION_NOTE_MAX } from '../../core/limits.js';\nimport { paginatedSchema } from '../../core/responses.js';\nimport { type ReportTargetKind, reportReasonSchema, reportTargetKindSchema } from '../feed/feed.js';\n\n/**\n * Where something is read in the app: a post or poll, or a product page (for a\n * product, and for a review of it).\n */\nexport const MODERATION_CONTEXT_KIND_VALUES = ['post', 'poll', 'product'] as const;\nexport const moderationContextKindSchema = z.enum(MODERATION_CONTEXT_KIND_VALUES);\nexport type ModerationContextKind = z.infer<typeof moderationContextKindSchema>;\n\n/** Where a post, poll, comment, product or review stands. */\nexport const MODERATION_STATE_VALUES = ['visible', 'hidden', 'removed'] as const;\nexport const moderationStateSchema = z.enum(MODERATION_STATE_VALUES);\nexport type ModerationState = z.infer<typeof moderationStateSchema>;\n\nexport const MODERATION_ACTION_VALUES = ['dismiss', 'hide', 'restore', 'remove'] as const;\nexport const moderationActionSchema = z.enum(MODERATION_ACTION_VALUES);\nexport type ModerationAction = z.infer<typeof moderationActionSchema>;\n\n/**\n * Which decisions apply to which kind of thing: everything, except that a\n * product is never removed by staff (it is its team's work; hiding it takes it\n * out of Discover and off every page but its team's). The decision dialog\n * offers these, and the server refuses anything else with\n * `MODERATION_ACTION_INVALID`. Exhaustive by type.\n */\nexport const MODERATION_ACTIONS_BY_TARGET_KIND = {\n post: MODERATION_ACTION_VALUES,\n poll: MODERATION_ACTION_VALUES,\n comment: MODERATION_ACTION_VALUES,\n review: MODERATION_ACTION_VALUES,\n product: ['dismiss', 'hide', 'restore'],\n} as const satisfies Record<ReportTargetKind, readonly ModerationAction[]>;\n\nconst count = z.number().int().min(0);\n\n/** One post, poll, comment, product or review as moderation sees it. */\nexport const moderationItemSchema = z.object({\n target: z.object({ kind: reportTargetKindSchema, id: objectIdSchema }),\n /**\n * Where it is read in the app: the post or poll itself, or the one a comment\n * is on; the product page for a product or a review.\n */\n content: z.object({ kind: moderationContextKindSchema, id: objectIdSchema }),\n /**\n * Its first words, cut to `MODERATION_EXCERPT_MAX`: a poll's question, a\n * product's name and tagline, a review's stars and text.\n */\n excerpt: z.string(),\n /** Who wrote it; for a product, its owner. */\n author: z.object({\n id: objectIdSchema,\n displayName: z.string(),\n avatarUrl: z.url().nullable(),\n }),\n createdAt: z.iso.datetime(),\n state: moderationStateSchema,\n reports: z.object({\n /** Reports nobody has decided on. */\n open: count,\n /** Every report ever filed about it. */\n total: count,\n /** Why it was reported, among the open ones, most-given first. */\n reasons: z.array(z.object({ reason: reportReasonSchema, count })),\n /** The reporters' notes, among the open ones, newest first (a few, not all). */\n notes: z.array(z.string()),\n /** When the oldest open report was filed. Null when none is open. */\n oldestOpenAt: z.iso.datetime().nullable(),\n }),\n /** What the asking staff member may do to it now. */\n viewer: z.object({\n canDismiss: z.boolean(),\n canHide: z.boolean(),\n canRestore: z.boolean(),\n canRemove: z.boolean(),\n }),\n});\nexport type ModerationItem = z.infer<typeof moderationItemSchema>;\n\nexport const moderationPageSchema = paginatedSchema(moderationItemSchema);\nexport type ModerationPage = z.infer<typeof moderationPageSchema>;\n\n/** `GET /moderation/content`. Visible when not said. */\nexport const moderationContentQuerySchema = paginationQuerySchema.extend({\n state: z.preprocess(emptyStringToUndefined, moderationStateSchema.default('visible')),\n});\nexport type ModerationContentQuery = z.infer<typeof moderationContentQuerySchema>;\nexport type ModerationContentQueryInput = z.input<typeof moderationContentQuerySchema>;\n\n/** `POST /moderation/:contentKind/:contentId/decision`: what is decided on. */\nexport const moderationTargetParamsSchema = z.object({\n contentKind: reportTargetKindSchema,\n contentId: objectIdSchema,\n});\nexport type ModerationTargetParams = z.infer<typeof moderationTargetParamsSchema>;\n\n/** `POST /moderation/:contentKind/:contentId/decision`. */\nexport const moderationDecisionBodySchema = z.object({\n action: moderationActionSchema,\n /** Why, for the audit log. Optional: an obvious spam removal needs no essay. */\n note: optionalTrimmedString(MODERATION_NOTE_MAX),\n});\nexport type ModerationDecisionBody = z.infer<typeof moderationDecisionBodySchema>;\n"]}
@@ -0,0 +1,212 @@
1
+ /**
2
+ * founder-schema — Notifications: the bell
3
+ *
4
+ * The design the user approved on 2026-10-05 ("Founder Circle Schema Review"):
5
+ *
6
+ * - ONE ROW PER RECIPIENT, written when the event happens.
7
+ * - REPEATS MERGE: a second comment on your post updates the same row
8
+ * ("Asha and 2 others commented"), it never adds one. `count` is how many
9
+ * events it holds and `actor` the latest.
10
+ * - THE ACTOR IS LOOKED UP WHEN READ, never copied in: a renamed member shows
11
+ * their new name, and a deleted one shows as null ("A former member").
12
+ * - PREFERENCES ARE CHECKED before writing (Jansathi stores them and never
13
+ * reads them). A team invitation is always delivered: it needs an answer.
14
+ * - KEPT `NOTIFICATION_RETENTION_DAYS`, then deleted.
15
+ * - Nobody is notified of their own action.
16
+ *
17
+ * In-app only. Push stays chat's (async-service) until a channel is designed:
18
+ * a client polls the unread count (on focus, and every minute while visible).
19
+ *
20
+ * Routes (all signed in):
21
+ * GET /api/v1/notifications paginationQuerySchema -> notificationPageSchema
22
+ * GET /api/v1/notifications/unread-count -> notificationUnreadCountSchema
23
+ * POST /api/v1/notifications/read mark every one read -> notificationUnreadCountSchema
24
+ * PUT /api/v1/notifications/:notificationId/read -> notificationSchema
25
+ * GET /api/v1/notifications/preferences -> notificationPreferencesSchema
26
+ * PUT /api/v1/notifications/preferences updateNotificationPreferencesBodySchema -> notificationPreferencesSchema
27
+ *
28
+ * @module founder-schema/domains/notifications
29
+ */
30
+ import { z } from 'zod';
31
+ /**
32
+ * What happened. ⚠ Stored per row: append only.
33
+ *
34
+ * comment_on_post someone commented on your post or poll
35
+ * reply_to_comment someone replied to your comment
36
+ * product_invite a team invited you (always delivered)
37
+ * product_invite_accepted someone accepted your team's invitation
38
+ * product_review someone reviewed a product you are on the team of
39
+ * review_reply a product's team answered your review
40
+ * feedback_helpful a team marked your feedback as having helped
41
+ * tester_confirmed a team confirmed you tested their product
42
+ */
43
+ export declare const NOTIFICATION_KIND_VALUES: readonly ["comment_on_post", "reply_to_comment", "product_invite", "product_invite_accepted", "product_review", "review_reply", "feedback_helpful", "tester_confirmed"];
44
+ export declare const notificationKindSchema: z.ZodEnum<{
45
+ feedback_helpful: "feedback_helpful";
46
+ tester_confirmed: "tester_confirmed";
47
+ comment_on_post: "comment_on_post";
48
+ reply_to_comment: "reply_to_comment";
49
+ product_invite: "product_invite";
50
+ product_invite_accepted: "product_invite_accepted";
51
+ product_review: "product_review";
52
+ review_reply: "review_reply";
53
+ }>;
54
+ export type NotificationKind = z.infer<typeof notificationKindSchema>;
55
+ /** The switches a member has, each covering one or more kinds. */
56
+ export declare const NOTIFICATION_PREFERENCE_VALUES: readonly ["comments", "replies", "productActivity", "recognition"];
57
+ export declare const notificationPreferenceSchema: z.ZodEnum<{
58
+ comments: "comments";
59
+ replies: "replies";
60
+ productActivity: "productActivity";
61
+ recognition: "recognition";
62
+ }>;
63
+ export type NotificationPreference = z.infer<typeof notificationPreferenceSchema>;
64
+ /**
65
+ * Which switch silences which kind. Null: always delivered. Exhaustive by
66
+ * type, so a kind added without a decision here does not compile.
67
+ */
68
+ export declare const NOTIFICATION_PREFERENCE_OF: {
69
+ readonly comment_on_post: "comments";
70
+ readonly reply_to_comment: "replies";
71
+ readonly product_invite: null;
72
+ readonly product_invite_accepted: "productActivity";
73
+ readonly product_review: "productActivity";
74
+ readonly review_reply: "productActivity";
75
+ readonly feedback_helpful: "recognition";
76
+ readonly tester_confirmed: "recognition";
77
+ };
78
+ /**
79
+ * One row of the bell. Which of the optional targets are present depends on
80
+ * `kind`:
81
+ *
82
+ * comment_on_post content, commentId
83
+ * reply_to_comment content, commentId (the reply)
84
+ * product_invite product, role
85
+ * product_invite_accepted product
86
+ * product_review product, reviewId
87
+ * review_reply product, reviewId
88
+ * feedback_helpful product, content, commentId
89
+ * tester_confirmed product
90
+ *
91
+ * A target that no longer exists (a deleted post, product or review) is
92
+ * absent rather than pointing at a 404; the row still says what happened.
93
+ */
94
+ export declare const notificationSchema: z.ZodObject<{
95
+ id: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
96
+ kind: z.ZodEnum<{
97
+ feedback_helpful: "feedback_helpful";
98
+ tester_confirmed: "tester_confirmed";
99
+ comment_on_post: "comment_on_post";
100
+ reply_to_comment: "reply_to_comment";
101
+ product_invite: "product_invite";
102
+ product_invite_accepted: "product_invite_accepted";
103
+ product_review: "product_review";
104
+ review_reply: "review_reply";
105
+ }>;
106
+ actor: z.ZodNullable<z.ZodObject<{
107
+ userId: z.ZodString;
108
+ username: z.ZodString;
109
+ displayName: z.ZodString;
110
+ avatarUrl: z.ZodOptional<z.ZodURL>;
111
+ headline: z.ZodOptional<z.ZodString>;
112
+ }, z.core.$strip>>;
113
+ count: z.ZodNumber;
114
+ content: z.ZodOptional<z.ZodObject<{
115
+ kind: z.ZodEnum<{
116
+ post: "post";
117
+ poll: "poll";
118
+ }>;
119
+ id: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
120
+ }, z.core.$strip>>;
121
+ commentId: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
122
+ product: z.ZodOptional<z.ZodObject<{
123
+ id: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
124
+ slug: z.ZodString;
125
+ name: z.ZodString;
126
+ logoUrl: z.ZodOptional<z.ZodURL>;
127
+ }, z.core.$strip>>;
128
+ reviewId: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
129
+ role: z.ZodOptional<z.ZodEnum<{
130
+ team: "team";
131
+ cofounder: "cofounder";
132
+ }>>;
133
+ excerpt: z.ZodOptional<z.ZodString>;
134
+ readAt: z.ZodNullable<z.ZodISODateTime>;
135
+ lastAt: z.ZodISODateTime;
136
+ createdAt: z.ZodISODateTime;
137
+ }, z.core.$strip>;
138
+ export type Notification = z.infer<typeof notificationSchema>;
139
+ export declare const notificationPageSchema: z.ZodObject<{
140
+ items: z.ZodArray<z.ZodObject<{
141
+ id: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
142
+ kind: z.ZodEnum<{
143
+ feedback_helpful: "feedback_helpful";
144
+ tester_confirmed: "tester_confirmed";
145
+ comment_on_post: "comment_on_post";
146
+ reply_to_comment: "reply_to_comment";
147
+ product_invite: "product_invite";
148
+ product_invite_accepted: "product_invite_accepted";
149
+ product_review: "product_review";
150
+ review_reply: "review_reply";
151
+ }>;
152
+ actor: z.ZodNullable<z.ZodObject<{
153
+ userId: z.ZodString;
154
+ username: z.ZodString;
155
+ displayName: z.ZodString;
156
+ avatarUrl: z.ZodOptional<z.ZodURL>;
157
+ headline: z.ZodOptional<z.ZodString>;
158
+ }, z.core.$strip>>;
159
+ count: z.ZodNumber;
160
+ content: z.ZodOptional<z.ZodObject<{
161
+ kind: z.ZodEnum<{
162
+ post: "post";
163
+ poll: "poll";
164
+ }>;
165
+ id: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
166
+ }, z.core.$strip>>;
167
+ commentId: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
168
+ product: z.ZodOptional<z.ZodObject<{
169
+ id: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
170
+ slug: z.ZodString;
171
+ name: z.ZodString;
172
+ logoUrl: z.ZodOptional<z.ZodURL>;
173
+ }, z.core.$strip>>;
174
+ reviewId: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
175
+ role: z.ZodOptional<z.ZodEnum<{
176
+ team: "team";
177
+ cofounder: "cofounder";
178
+ }>>;
179
+ excerpt: z.ZodOptional<z.ZodString>;
180
+ readAt: z.ZodNullable<z.ZodISODateTime>;
181
+ lastAt: z.ZodISODateTime;
182
+ createdAt: z.ZodISODateTime;
183
+ }, z.core.$strip>>;
184
+ nextCursor: z.ZodNullable<z.ZodString>;
185
+ }, z.core.$strip>;
186
+ export type NotificationPage = z.infer<typeof notificationPageSchema>;
187
+ /** The badge. */
188
+ export declare const notificationUnreadCountSchema: z.ZodObject<{
189
+ unread: z.ZodNumber;
190
+ }, z.core.$strip>;
191
+ export type NotificationUnreadCount = z.infer<typeof notificationUnreadCountSchema>;
192
+ export declare const notificationParamsSchema: z.ZodObject<{
193
+ notificationId: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
194
+ }, z.core.$strip>;
195
+ export type NotificationParams = z.infer<typeof notificationParamsSchema>;
196
+ /** Every switch, as stored (on by default). */
197
+ export declare const notificationPreferencesSchema: z.ZodObject<{
198
+ comments: z.ZodBoolean;
199
+ replies: z.ZodBoolean;
200
+ productActivity: z.ZodBoolean;
201
+ recognition: z.ZodBoolean;
202
+ }, z.core.$strip>;
203
+ export type NotificationPreferences = z.infer<typeof notificationPreferencesSchema>;
204
+ /** Only the switches that changed; no defaults (a patch states what was sent). */
205
+ export declare const updateNotificationPreferencesBodySchema: z.ZodObject<{
206
+ comments: z.ZodOptional<z.ZodBoolean>;
207
+ replies: z.ZodOptional<z.ZodBoolean>;
208
+ productActivity: z.ZodOptional<z.ZodBoolean>;
209
+ recognition: z.ZodOptional<z.ZodBoolean>;
210
+ }, z.core.$strip>;
211
+ export type UpdateNotificationPreferencesBody = z.infer<typeof updateNotificationPreferencesBodySchema>;
212
+ //# sourceMappingURL=notifications.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../../../src/domains/notifications/notifications.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAQxB;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,wBAAwB,yKAS3B,CAAC;AACX,eAAO,MAAM,sBAAsB;;;;;;;;;EAAmC,CAAC;AACvE,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,kEAAkE;AAClE,eAAO,MAAM,8BAA8B,oEAKjC,CAAC;AACX,eAAO,MAAM,4BAA4B;;;;;EAAyC,CAAC;AACnF,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAElF;;;GAGG;AACH,eAAO,MAAM,0BAA0B;;;;;;;;;CASqC,CAAC;AAE7E;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA6B7B,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAsC,CAAC;AAC1E,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,iBAAiB;AACjB,eAAO,MAAM,6BAA6B;;iBAExC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;AAEpF,eAAO,MAAM,wBAAwB;;iBAA+C,CAAC;AACrF,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,+CAA+C;AAC/C,eAAO,MAAM,6BAA6B;;;;;iBAKxC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;AAEpF,kFAAkF;AAClF,eAAO,MAAM,uCAAuC;;;;;iBAKlD,CAAC;AACH,MAAM,MAAM,iCAAiC,GAAG,CAAC,CAAC,KAAK,CACrD,OAAO,uCAAuC,CAC/C,CAAC"}
@@ -0,0 +1,148 @@
1
+ /**
2
+ * founder-schema — Notifications: the bell
3
+ *
4
+ * The design the user approved on 2026-10-05 ("Founder Circle Schema Review"):
5
+ *
6
+ * - ONE ROW PER RECIPIENT, written when the event happens.
7
+ * - REPEATS MERGE: a second comment on your post updates the same row
8
+ * ("Asha and 2 others commented"), it never adds one. `count` is how many
9
+ * events it holds and `actor` the latest.
10
+ * - THE ACTOR IS LOOKED UP WHEN READ, never copied in: a renamed member shows
11
+ * their new name, and a deleted one shows as null ("A former member").
12
+ * - PREFERENCES ARE CHECKED before writing (Jansathi stores them and never
13
+ * reads them). A team invitation is always delivered: it needs an answer.
14
+ * - KEPT `NOTIFICATION_RETENTION_DAYS`, then deleted.
15
+ * - Nobody is notified of their own action.
16
+ *
17
+ * In-app only. Push stays chat's (async-service) until a channel is designed:
18
+ * a client polls the unread count (on focus, and every minute while visible).
19
+ *
20
+ * Routes (all signed in):
21
+ * GET /api/v1/notifications paginationQuerySchema -> notificationPageSchema
22
+ * GET /api/v1/notifications/unread-count -> notificationUnreadCountSchema
23
+ * POST /api/v1/notifications/read mark every one read -> notificationUnreadCountSchema
24
+ * PUT /api/v1/notifications/:notificationId/read -> notificationSchema
25
+ * GET /api/v1/notifications/preferences -> notificationPreferencesSchema
26
+ * PUT /api/v1/notifications/preferences updateNotificationPreferencesBodySchema -> notificationPreferencesSchema
27
+ *
28
+ * @module founder-schema/domains/notifications
29
+ */
30
+ import { z } from 'zod';
31
+ import { objectIdSchema } from '../../core/common.js';
32
+ import { NOTIFICATION_EXCERPT_MAX } from '../../core/limits.js';
33
+ import { paginatedSchema } from '../../core/responses.js';
34
+ import { contentKindSchema } from '../feed/feed.js';
35
+ import { productChipSchema, productInvitableRoleSchema } from '../products/products.js';
36
+ import { memberBylineSchema } from '../profile/founder-profile.js';
37
+ /**
38
+ * What happened. ⚠ Stored per row: append only.
39
+ *
40
+ * comment_on_post someone commented on your post or poll
41
+ * reply_to_comment someone replied to your comment
42
+ * product_invite a team invited you (always delivered)
43
+ * product_invite_accepted someone accepted your team's invitation
44
+ * product_review someone reviewed a product you are on the team of
45
+ * review_reply a product's team answered your review
46
+ * feedback_helpful a team marked your feedback as having helped
47
+ * tester_confirmed a team confirmed you tested their product
48
+ */
49
+ export const NOTIFICATION_KIND_VALUES = [
50
+ 'comment_on_post',
51
+ 'reply_to_comment',
52
+ 'product_invite',
53
+ 'product_invite_accepted',
54
+ 'product_review',
55
+ 'review_reply',
56
+ 'feedback_helpful',
57
+ 'tester_confirmed',
58
+ ];
59
+ export const notificationKindSchema = z.enum(NOTIFICATION_KIND_VALUES);
60
+ /** The switches a member has, each covering one or more kinds. */
61
+ export const NOTIFICATION_PREFERENCE_VALUES = [
62
+ 'comments',
63
+ 'replies',
64
+ 'productActivity',
65
+ 'recognition',
66
+ ];
67
+ export const notificationPreferenceSchema = z.enum(NOTIFICATION_PREFERENCE_VALUES);
68
+ /**
69
+ * Which switch silences which kind. Null: always delivered. Exhaustive by
70
+ * type, so a kind added without a decision here does not compile.
71
+ */
72
+ export const NOTIFICATION_PREFERENCE_OF = {
73
+ comment_on_post: 'comments',
74
+ reply_to_comment: 'replies',
75
+ product_invite: null,
76
+ product_invite_accepted: 'productActivity',
77
+ product_review: 'productActivity',
78
+ review_reply: 'productActivity',
79
+ feedback_helpful: 'recognition',
80
+ tester_confirmed: 'recognition',
81
+ };
82
+ /**
83
+ * One row of the bell. Which of the optional targets are present depends on
84
+ * `kind`:
85
+ *
86
+ * comment_on_post content, commentId
87
+ * reply_to_comment content, commentId (the reply)
88
+ * product_invite product, role
89
+ * product_invite_accepted product
90
+ * product_review product, reviewId
91
+ * review_reply product, reviewId
92
+ * feedback_helpful product, content, commentId
93
+ * tester_confirmed product
94
+ *
95
+ * A target that no longer exists (a deleted post, product or review) is
96
+ * absent rather than pointing at a 404; the row still says what happened.
97
+ */
98
+ export const notificationSchema = z.object({
99
+ id: objectIdSchema,
100
+ kind: notificationKindSchema,
101
+ /** The latest person to do it, live; null for a deleted account. */
102
+ actor: memberBylineSchema.nullable(),
103
+ /**
104
+ * How many EVENTS this row merges, at least 1: three comments by one person
105
+ * are 3. Word it as events ("3 new comments, latest from Asha"), never as
106
+ * people ("Asha and 2 others"), which this count does not know.
107
+ */
108
+ count: z.number().int().positive(),
109
+ content: z.object({ kind: contentKindSchema, id: objectIdSchema }).optional(),
110
+ commentId: objectIdSchema.optional(),
111
+ product: productChipSchema.optional(),
112
+ reviewId: objectIdSchema.optional(),
113
+ /** For `product_invite`: the role offered. */
114
+ role: productInvitableRoleSchema.optional(),
115
+ /**
116
+ * The first words of what was written, for the comment and review kinds
117
+ * (the latest one, on a merged row), cut to `NOTIFICATION_EXCERPT_MAX`. Gone
118
+ * when that comment or review is deleted or hidden, and when its writer's
119
+ * account is.
120
+ */
121
+ excerpt: z.string().max(NOTIFICATION_EXCERPT_MAX).optional(),
122
+ /** Null until opened (or until "mark all read"). A merged repeat makes it unread again. */
123
+ readAt: z.iso.datetime().nullable(),
124
+ /** When it last happened: the list's order. */
125
+ lastAt: z.iso.datetime(),
126
+ createdAt: z.iso.datetime(),
127
+ });
128
+ export const notificationPageSchema = paginatedSchema(notificationSchema);
129
+ /** The badge. */
130
+ export const notificationUnreadCountSchema = z.object({
131
+ unread: z.number().int().nonnegative(),
132
+ });
133
+ export const notificationParamsSchema = z.object({ notificationId: objectIdSchema });
134
+ /** Every switch, as stored (on by default). */
135
+ export const notificationPreferencesSchema = z.object({
136
+ comments: z.boolean(),
137
+ replies: z.boolean(),
138
+ productActivity: z.boolean(),
139
+ recognition: z.boolean(),
140
+ });
141
+ /** Only the switches that changed; no defaults (a patch states what was sent). */
142
+ export const updateNotificationPreferencesBodySchema = z.object({
143
+ comments: z.boolean().optional(),
144
+ replies: z.boolean().optional(),
145
+ productActivity: z.boolean().optional(),
146
+ recognition: z.boolean().optional(),
147
+ });
148
+ //# sourceMappingURL=notifications.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notifications.js","sourceRoot":"","sources":["../../../src/domains/notifications/notifications.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * founder-schema — Notifications: the bell\n *\n * The design the user approved on 2026-10-05 (\"Founder Circle Schema Review\"):\n *\n * - ONE ROW PER RECIPIENT, written when the event happens.\n * - REPEATS MERGE: a second comment on your post updates the same row\n * (\"Asha and 2 others commented\"), it never adds one. `count` is how many\n * events it holds and `actor` the latest.\n * - THE ACTOR IS LOOKED UP WHEN READ, never copied in: a renamed member shows\n * their new name, and a deleted one shows as null (\"A former member\").\n * - PREFERENCES ARE CHECKED before writing (Jansathi stores them and never\n * reads them). A team invitation is always delivered: it needs an answer.\n * - KEPT `NOTIFICATION_RETENTION_DAYS`, then deleted.\n * - Nobody is notified of their own action.\n *\n * In-app only. Push stays chat's (async-service) until a channel is designed:\n * a client polls the unread count (on focus, and every minute while visible).\n *\n * Routes (all signed in):\n * GET /api/v1/notifications paginationQuerySchema -> notificationPageSchema\n * GET /api/v1/notifications/unread-count -> notificationUnreadCountSchema\n * POST /api/v1/notifications/read mark every one read -> notificationUnreadCountSchema\n * PUT /api/v1/notifications/:notificationId/read -> notificationSchema\n * GET /api/v1/notifications/preferences -> notificationPreferencesSchema\n * PUT /api/v1/notifications/preferences updateNotificationPreferencesBodySchema -> notificationPreferencesSchema\n *\n * @module founder-schema/domains/notifications\n */\n\nimport { z } from 'zod';\nimport { objectIdSchema } from '../../core/common.js';\nimport { NOTIFICATION_EXCERPT_MAX } from '../../core/limits.js';\nimport { paginatedSchema } from '../../core/responses.js';\nimport { contentKindSchema } from '../feed/feed.js';\nimport { productChipSchema, productInvitableRoleSchema } from '../products/products.js';\nimport { memberBylineSchema } from '../profile/founder-profile.js';\n\n/**\n * What happened. ⚠ Stored per row: append only.\n *\n * comment_on_post someone commented on your post or poll\n * reply_to_comment someone replied to your comment\n * product_invite a team invited you (always delivered)\n * product_invite_accepted someone accepted your team's invitation\n * product_review someone reviewed a product you are on the team of\n * review_reply a product's team answered your review\n * feedback_helpful a team marked your feedback as having helped\n * tester_confirmed a team confirmed you tested their product\n */\nexport const NOTIFICATION_KIND_VALUES = [\n 'comment_on_post',\n 'reply_to_comment',\n 'product_invite',\n 'product_invite_accepted',\n 'product_review',\n 'review_reply',\n 'feedback_helpful',\n 'tester_confirmed',\n] as const;\nexport const notificationKindSchema = z.enum(NOTIFICATION_KIND_VALUES);\nexport type NotificationKind = z.infer<typeof notificationKindSchema>;\n\n/** The switches a member has, each covering one or more kinds. */\nexport const NOTIFICATION_PREFERENCE_VALUES = [\n 'comments',\n 'replies',\n 'productActivity',\n 'recognition',\n] as const;\nexport const notificationPreferenceSchema = z.enum(NOTIFICATION_PREFERENCE_VALUES);\nexport type NotificationPreference = z.infer<typeof notificationPreferenceSchema>;\n\n/**\n * Which switch silences which kind. Null: always delivered. Exhaustive by\n * type, so a kind added without a decision here does not compile.\n */\nexport const NOTIFICATION_PREFERENCE_OF = {\n comment_on_post: 'comments',\n reply_to_comment: 'replies',\n product_invite: null,\n product_invite_accepted: 'productActivity',\n product_review: 'productActivity',\n review_reply: 'productActivity',\n feedback_helpful: 'recognition',\n tester_confirmed: 'recognition',\n} as const satisfies Record<NotificationKind, NotificationPreference | null>;\n\n/**\n * One row of the bell. Which of the optional targets are present depends on\n * `kind`:\n *\n * comment_on_post content, commentId\n * reply_to_comment content, commentId (the reply)\n * product_invite product, role\n * product_invite_accepted product\n * product_review product, reviewId\n * review_reply product, reviewId\n * feedback_helpful product, content, commentId\n * tester_confirmed product\n *\n * A target that no longer exists (a deleted post, product or review) is\n * absent rather than pointing at a 404; the row still says what happened.\n */\nexport const notificationSchema = z.object({\n id: objectIdSchema,\n kind: notificationKindSchema,\n /** The latest person to do it, live; null for a deleted account. */\n actor: memberBylineSchema.nullable(),\n /**\n * How many EVENTS this row merges, at least 1: three comments by one person\n * are 3. Word it as events (\"3 new comments, latest from Asha\"), never as\n * people (\"Asha and 2 others\"), which this count does not know.\n */\n count: z.number().int().positive(),\n content: z.object({ kind: contentKindSchema, id: objectIdSchema }).optional(),\n commentId: objectIdSchema.optional(),\n product: productChipSchema.optional(),\n reviewId: objectIdSchema.optional(),\n /** For `product_invite`: the role offered. */\n role: productInvitableRoleSchema.optional(),\n /**\n * The first words of what was written, for the comment and review kinds\n * (the latest one, on a merged row), cut to `NOTIFICATION_EXCERPT_MAX`. Gone\n * when that comment or review is deleted or hidden, and when its writer's\n * account is.\n */\n excerpt: z.string().max(NOTIFICATION_EXCERPT_MAX).optional(),\n /** Null until opened (or until \"mark all read\"). A merged repeat makes it unread again. */\n readAt: z.iso.datetime().nullable(),\n /** When it last happened: the list's order. */\n lastAt: z.iso.datetime(),\n createdAt: z.iso.datetime(),\n});\nexport type Notification = z.infer<typeof notificationSchema>;\n\nexport const notificationPageSchema = paginatedSchema(notificationSchema);\nexport type NotificationPage = z.infer<typeof notificationPageSchema>;\n\n/** The badge. */\nexport const notificationUnreadCountSchema = z.object({\n unread: z.number().int().nonnegative(),\n});\nexport type NotificationUnreadCount = z.infer<typeof notificationUnreadCountSchema>;\n\nexport const notificationParamsSchema = z.object({ notificationId: objectIdSchema });\nexport type NotificationParams = z.infer<typeof notificationParamsSchema>;\n\n/** Every switch, as stored (on by default). */\nexport const notificationPreferencesSchema = z.object({\n comments: z.boolean(),\n replies: z.boolean(),\n productActivity: z.boolean(),\n recognition: z.boolean(),\n});\nexport type NotificationPreferences = z.infer<typeof notificationPreferencesSchema>;\n\n/** Only the switches that changed; no defaults (a patch states what was sent). */\nexport const updateNotificationPreferencesBodySchema = z.object({\n comments: z.boolean().optional(),\n replies: z.boolean().optional(),\n productActivity: z.boolean().optional(),\n recognition: z.boolean().optional(),\n});\nexport type UpdateNotificationPreferencesBody = z.infer<\n typeof updateNotificationPreferencesBodySchema\n>;\n"]}
@@ -16,10 +16,12 @@
16
16
  * for the end. Letting those two drift is how a wizard says a step is optional
17
17
  * and then answers 422 at Finish.
18
18
  *
19
- * NO COPY IN HERE. Step titles, help lines and topic category names are
20
- * translation KEYS (`titleKey`, `helpKey`, `labelKey`), Jansathi's rule for
21
- * every config string: an English sentence in a shared package is a sentence
22
- * every other language has to match by wording.
19
+ * NO COPY IN HERE. Step titles and help lines are translation KEYS
20
+ * (`titleKey`, `helpKey`), Jansathi's rule for every config string: an English
21
+ * sentence in a shared package is a sentence every other language has to match
22
+ * by wording. A topic category is named by its `key`, which the client words
23
+ * (0.6.0 dropped its `labelKey`: the web, English-only since 2026-10-06, read
24
+ * nothing from it).
23
25
  *
24
26
  * @module founder-schema/domains/onboarding
25
27
  */
@@ -182,16 +184,11 @@ export type Topic = z.infer<typeof topicSchema>;
182
184
  export declare const TOPIC_CATEGORY_KEYS: readonly ["career", "tech", "business", "growth", "creative", "impact"];
183
185
  export type TopicCategoryKey = (typeof TOPIC_CATEGORY_KEYS)[number];
184
186
  /**
185
- * ⚠ TRANSLATION KEY SHAPE, for whoever builds the picker: `topics.category.<key>`
186
- * for a category and `topics.value.<topic>` for a topic. The dating product's
187
- * interests JSON is keyed by CATEGORY NAME instead, so all five of its label
188
- * lookups miss and every chip renders its raw English in every locale, with a
189
- * fully translated Hindi file sitting unreachable.
187
+ * One group of the picker: its `key` (the client words it) and its topics.
188
+ * Breaking in 0.6.0: no `labelKey`.
190
189
  */
191
190
  export interface TopicCategory {
192
191
  key: TopicCategoryKey;
193
- /** A translation key. The English lives in the client's message files, not here. */
194
- labelKey: `topics.category.${TopicCategoryKey}`;
195
192
  values: readonly Topic[];
196
193
  }
197
194
  export declare const TOPIC_CATEGORIES: readonly TopicCategory[];
@@ -593,6 +590,7 @@ export declare const completeOnboardingBodySchema: z.ZodObject<{
593
590
  community: "community";
594
591
  }>>>;
595
592
  venture: z.ZodOptional<z.ZodObject<{
593
+ tagline: z.ZodPreprocess<z.ZodOptional<z.ZodString>, unknown>;
596
594
  name: z.ZodString;
597
595
  industry: z.ZodEnum<{
598
596
  agritech: "agritech";
@@ -627,7 +625,6 @@ export declare const completeOnboardingBodySchema: z.ZodObject<{
627
625
  profitable: "profitable";
628
626
  exited: "exited";
629
627
  }>;
630
- tagline: z.ZodPreprocess<z.ZodOptional<z.ZodString>, unknown>;
631
628
  website: z.ZodOptional<z.ZodURL>;
632
629
  teamSize: z.ZodOptional<z.ZodNumber>;
633
630
  }, z.core.$strip>>;
@@ -792,6 +789,7 @@ export declare const ONBOARDING_STEP_SCHEMAS: {
792
789
  }, z.core.$strip>;
793
790
  readonly business: z.ZodObject<{
794
791
  venture: z.ZodOptional<z.ZodObject<{
792
+ tagline: z.ZodPreprocess<z.ZodOptional<z.ZodString>, unknown>;
795
793
  name: z.ZodString;
796
794
  industry: z.ZodEnum<{
797
795
  agritech: "agritech";
@@ -826,7 +824,6 @@ export declare const ONBOARDING_STEP_SCHEMAS: {
826
824
  profitable: "profitable";
827
825
  exited: "exited";
828
826
  }>;
829
- tagline: z.ZodPreprocess<z.ZodOptional<z.ZodString>, unknown>;
830
827
  website: z.ZodOptional<z.ZodURL>;
831
828
  teamSize: z.ZodOptional<z.ZodNumber>;
832
829
  }, z.core.$strip>>;
@@ -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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"}
@@ -16,10 +16,12 @@
16
16
  * for the end. Letting those two drift is how a wizard says a step is optional
17
17
  * and then answers 422 at Finish.
18
18
  *
19
- * NO COPY IN HERE. Step titles, help lines and topic category names are
20
- * translation KEYS (`titleKey`, `helpKey`, `labelKey`), Jansathi's rule for
21
- * every config string: an English sentence in a shared package is a sentence
22
- * every other language has to match by wording.
19
+ * NO COPY IN HERE. Step titles and help lines are translation KEYS
20
+ * (`titleKey`, `helpKey`), Jansathi's rule for every config string: an English
21
+ * sentence in a shared package is a sentence every other language has to match
22
+ * by wording. A topic category is named by its `key`, which the client words
23
+ * (0.6.0 dropped its `labelKey`: the web, English-only since 2026-10-06, read
24
+ * nothing from it).
23
25
  *
24
26
  * @module founder-schema/domains/onboarding
25
27
  */
@@ -223,7 +225,6 @@ export const TOPIC_CATEGORY_KEYS = [
223
225
  ];
224
226
  const topicCategory = (key, values) => ({
225
227
  key,
226
- labelKey: `topics.category.${key}`,
227
228
  values,
228
229
  });
229
230
  export const TOPIC_CATEGORIES = [