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.
- package/README.md +67 -0
- package/dist/access/permissions.d.ts +24 -2
- package/dist/access/permissions.d.ts.map +1 -1
- package/dist/access/permissions.js +9 -0
- package/dist/access/permissions.js.map +1 -1
- package/dist/core/common.d.ts +44 -0
- package/dist/core/common.d.ts.map +1 -1
- package/dist/core/common.js +76 -0
- package/dist/core/common.js.map +1 -1
- package/dist/core/errors.d.ts +29 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +52 -1
- package/dist/core/errors.js.map +1 -1
- package/dist/core/limits.d.ts +53 -0
- package/dist/core/limits.d.ts.map +1 -1
- package/dist/core/limits.js +57 -0
- package/dist/core/limits.js.map +1 -1
- package/dist/core/responses.d.ts +43 -0
- package/dist/core/responses.d.ts.map +1 -1
- package/dist/core/responses.js +3 -1
- package/dist/core/responses.js.map +1 -1
- package/dist/core/validation.d.ts +14 -0
- package/dist/core/validation.d.ts.map +1 -1
- package/dist/core/validation.js +14 -0
- package/dist/core/validation.js.map +1 -1
- package/dist/domains/audit/audit.d.ts +4 -0
- package/dist/domains/audit/audit.d.ts.map +1 -1
- package/dist/domains/audit/audit.js +6 -3
- package/dist/domains/audit/audit.js.map +1 -1
- package/dist/domains/contributions/contributions.d.ts +165 -0
- package/dist/domains/contributions/contributions.d.ts.map +1 -0
- package/dist/domains/contributions/contributions.js +103 -0
- package/dist/domains/contributions/contributions.js.map +1 -0
- package/dist/domains/discover/discover.d.ts +5 -0
- package/dist/domains/discover/discover.d.ts.map +1 -1
- package/dist/domains/discover/discover.js +3 -13
- package/dist/domains/discover/discover.js.map +1 -1
- package/dist/domains/feed/feed.d.ts +231 -7
- package/dist/domains/feed/feed.d.ts.map +1 -1
- package/dist/domains/feed/feed.js +86 -18
- package/dist/domains/feed/feed.js.map +1 -1
- package/dist/domains/media/media.d.ts +30 -5
- package/dist/domains/media/media.d.ts.map +1 -1
- package/dist/domains/media/media.js +32 -6
- package/dist/domains/media/media.js.map +1 -1
- package/dist/domains/members/members.d.ts +5 -5
- package/dist/domains/moderation/moderation.d.ts +53 -3
- package/dist/domains/moderation/moderation.d.ts.map +1 -1
- package/dist/domains/moderation/moderation.js +47 -8
- package/dist/domains/moderation/moderation.js.map +1 -1
- package/dist/domains/notifications/notifications.d.ts +212 -0
- package/dist/domains/notifications/notifications.d.ts.map +1 -0
- package/dist/domains/notifications/notifications.js +148 -0
- package/dist/domains/notifications/notifications.js.map +1 -0
- package/dist/domains/onboarding/onboarding.d.ts +10 -13
- package/dist/domains/onboarding/onboarding.d.ts.map +1 -1
- package/dist/domains/onboarding/onboarding.js +6 -5
- package/dist/domains/onboarding/onboarding.js.map +1 -1
- package/dist/domains/products/products.d.ts +1464 -0
- package/dist/domains/products/products.d.ts.map +1 -0
- package/dist/domains/products/products.js +474 -0
- package/dist/domains/products/products.js.map +1 -0
- package/dist/domains/profile/founder-profile.d.ts +43 -0
- package/dist/domains/profile/founder-profile.d.ts.map +1 -1
- package/dist/domains/profile/founder-profile.js +48 -0
- package/dist/domains/profile/founder-profile.js.map +1 -1
- package/dist/domains/reviews/reviews.d.ts +184 -0
- package/dist/domains/reviews/reviews.d.ts.map +1 -0
- package/dist/domains/reviews/reviews.js +99 -0
- package/dist/domains/reviews/reviews.js.map +1 -0
- package/dist/domains/uploads/uploads.d.ts +2 -0
- package/dist/domains/uploads/uploads.d.ts.map +1 -1
- package/dist/index.d.ts +10 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -6
- package/dist/index.js.map +1 -1
- 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
|
|
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 {
|
|
39
|
-
/**
|
|
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
|
|
72
|
+
/** One post, poll, comment, product or review as moderation sees it. */
|
|
46
73
|
export const moderationItemSchema = z.object({
|
|
47
|
-
target: z.object({ kind:
|
|
48
|
-
/**
|
|
49
|
-
|
|
50
|
-
|
|
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":"AAAA
|
|
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
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
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
|
-
*
|
|
186
|
-
*
|
|
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":"AAAA
|
|
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
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
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 = [
|