@odla-ai/chapter 0.61.0 → 0.63.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.
@@ -1,899 +0,0 @@
1
- import { CrmConfig, Crm, FieldConditions } from '@odla-ai/crm';
2
- import * as preact from 'preact';
3
- import { ComponentChildren } from 'preact';
4
-
5
- /**
6
- * A tier as declared in `defineChapter`, before it becomes a row.
7
- *
8
- * `groupId` is absent here because the chapter owns exactly one group at seed
9
- * time; the seeder fills it in.
10
- */
11
- interface ChapterTierConfig {
12
- id: string;
13
- name: string;
14
- priceCents: number;
15
- currency?: string;
16
- interval?: "year" | "month";
17
- stripePriceId?: string;
18
- blurb?: string;
19
- /** Defaults to declaration order when omitted. */
20
- sortOrder?: number;
21
- /** Defaults to true; set false to retire a tier without deleting it. */
22
- active?: boolean;
23
- }
24
-
25
- /** A code-declared mail event. Copy stays in settings; this describes who receives it and the values the event supplies. */
26
- interface ChapterEmailEvent {
27
- label: string;
28
- trigger: string;
29
- audience: string;
30
- variables: readonly string[];
31
- /** Values deliberately allowed to render empty, such as a free tier's amount suffix. */
32
- optionalVariables?: readonly string[];
33
- /** Require a resolved tier display name even when the copy does not mention it. */
34
- requiresTier?: boolean;
35
- }
36
-
37
- type TextFields$1<Key extends string> = {
38
- [Field in Key]: string;
39
- };
40
- /** User-visible language for sharing, delivery state, and leader rollups. */
41
- type AdminNetworkCopy = TextFields$1<"title" | "allowlistDescription" | "sharing" | "shareWith" | "shared" | "shareFailed" | "deliveryFailed" | "loadingRollup" | "loadRollupFailed" | "overview" | "configuredFollowers" | "reachableFollowers" | "recordsAcrossFollowers" | "noFollowers" | "followers" | "available" | "unavailable" | "transportReachable" | "transportUnavailable" | "transportNotChecked" | "edgeUnpaired" | "edgeUnpairedHint" | "bindingTransport" | "urlTransport" | "activeConfiguration" | "noActiveConfiguration" | "publicSiteAvailable" | "publicSiteUnavailable" | "publicSiteNotChecked" | "recordTotals" | "noRecordTotals">;
42
-
43
- /** Copy for the person record panels: lifecycle actions, comms, scheduling, billing and meetings. */
44
- type AdminRecordsCopy = {
45
- [Field in AdminRecordsCopyKey]: string;
46
- };
47
- /** Every key a host may override under `admin.records`. */
48
- type AdminRecordsCopyKey = "workflowMissing" | "invitation" | "comms" | "commsHistory" | "scheduling" | "billing" | "notes" | "access" | "sharing" | "lifecycle" | "role" | "superAdminHint" | "noAccount" | "approve" | "decline" | "refund" | "approved" | "declined" | "refunded" | "roleSet" | "messageSent" | "messageNotSent" | "loadingTemplates" | "template" | "chooseTemplate" | "sending" | "send" | "noMessages" | "message" | "newStartPrompt" | "parseFailed" | "cancelConfirm" | "callCancelled" | "callRescheduled" | "noApplication" | "loadingMeetings" | "noMeetings" | "joinMeeting" | "openCalendar" | "reschedule" | "cancel";
49
-
50
- type TextFields<Key extends string> = {
51
- [Field in Key]: string;
52
- };
53
-
54
- /** Complete serializable language for Chapter surfaces; use render slots for markup and `formatChapterCopy()` for placeholders. */
55
- interface ChapterCopy {
56
- common: TextFields<"loading" | "saved" | "unknown">;
57
- join: {
58
- /** Prepaid invitation attribution, consent and recoverable error copy. */
59
- namedSeat?: TextFields<"title" | "from" | "prepaid" | "body" | "consent" | "accept" | "accepting" | "loading" | "unavailable" | "retry" | "failure">;
60
- form: TextFields<"submit" | "submitting" | "submitFailed" | "unexpectedFailure">;
61
- booking: TextFields<"unavailable" | "loadFailed" | "slotTaken" | "failed" | "loading" | "book" | "booking"> & {
62
- title?: string;
63
- intro?: string;
64
- /** Shown under the booking button until a date is chosen. */
65
- pickDate?: string;
66
- /** Shown under the booking button once a date is chosen, until a time is. */
67
- pickTime?: string;
68
- };
69
- /**
70
- * `consent` is the affirmative statement a member agrees to before the card
71
- * form appears, rendered beside the checkbox and beneath the policy text.
72
- * It is what the member is shown at the moment of consent, so it says they
73
- * agree rather than merely restating the policy. Set it to an empty string
74
- * to render the checkbox with the policy as its only label.
75
- */
76
- payment: TextFields<"setupFailed" | "preparing" | "pending" | "processing" | "payAndContinue" | "incomplete" | "consent" | "foundingMember" | "foundingDiscount" | "standardRate" | "yourRate">;
77
- done: TextFields<"label" | "calendarInvite" | "memberArea">;
78
- accepted: TextFields<"label" | "body">;
79
- gift: TextFields<"chooseSelf" | "optionsLabel" | "label" | "body" | "save" | "rotate" | "rotateFailed" | "claimTitle" | "claimBody" | "code" | "redeem" | "redeemed" | "redeemFailed">;
80
- };
81
- members: {
82
- loadFailed: string;
83
- account: TextFields<"signOut" | "adminConsole">;
84
- provisional: TextFields<"cardLabel" | "applicationNeeded" | "applicationNeededBody" | "apply" | "refunded" | "refundedBody" | "active" | "renews" | "introductionCall" | "calendarInvite" | "joinCall" | "bookCall" | "bookCallBody" | "chooseTime">;
85
- full: TextFields<"welcome">;
86
- gifts: TextFields<"title" | "body" | "noneAvailable" | "paid" | "saveCode" | "purchases" | "noPurchases" | "pending" | "redeemed" | "expired" | "revoked" | "rotate" | "rotated" | "rotateFailed" | "redeemTitle" | "code" | "redeem" | "redeemFailed">;
87
- reschedule: TextFields<"changeTime" | "unavailable" | "loadFailed" | "slotGone" | "loading" | "noTimes" | "rescheduling" | "keepTime">;
88
- };
89
- admin: {
90
- auth: TextFields<"checking" | "notAuthorized" | "accountNotAuthorized" | "thisAccount" | "signOut" | "loading" | "signInNotConfigured" | "missingPublishableKey" | "noWorkspaces" | "signInTagline">;
91
- shell: TextFields<"adminRole" | "adminConsole" | "adminName" | "navigationLabel">;
92
- workspaces: TextFields<"dashboard" | "overview" | "billing" | "people" | "admission" | "network" | "portfolio" | "settings" | "calendar" | "email" | "chapters" | "formations" | "dealFlow" | "dashboardViewsLabel" | "settingsViewsLabel" | "collectionsLabel" | "portfolioViewsLabel">;
93
- dashboard: TextFields<"loading" | "loadFailed" | "upcomingCalls" | "callsNeedAttention" | "noUpcomingCalls" | "drift" | "open" | "meet" | "applications" | "newMembers" | "newMembersUnavailable" | "revenueAdded" | "revenue" | "revenueUnavailable" | "pipeline" | "pipelineLabel" | "thisWeek" | "noChange" | "activeMemberships" | "annualRunRate" | "testMode">;
94
- billing: TextFields<"loading" | "loadFailed" | "notConfigured" | "active" | "annualized" | "renewingSoon" | "pastDue" | "subscriptions" | "testMode" | "truncated" | "name" | "email" | "application" | "subscription" | "cancelling" | "amount" | "renews">;
95
- availability: TextFields<"loading" | "loadFailed" | "saved" | "title" | "days" | "startHour" | "endHour" | "slotMinutes" | "minNoticeHours" | "windowDays" | "timezone" | "summaryTemplate" | "save"> & {
96
- dayLabels: readonly string[];
97
- };
98
- email: TextFields<"loading" | "loadFailed" | "saved" | "sent" | "testFailed" | "delivery" | "notificationAddress" | "replyTo" | "fromName" | "fromNameHint" | "spamRisk" | "debugInbox" | "debugInboxHint" | "sendTest" | "enabled" | "subject" | "body" | "htmlBody" | "htmlBodyHint" | "save" | "sendLog" | "sentColumn" | "templateColumn" | "statusColumn" | "toColumn" | "failed" | "redirected" | "delivered">;
99
- meetings: TextFields<"loading" | "loadFailed" | "cancelConfirm" | "newStartPrompt" | "parseFailed" | "agenda" | "empty" | "unknown" | "drift" | "meet" | "reschedule" | "cancel">;
100
- admission: TextFields<"title" | "description" | "email" | "decision" | "expires" | "note" | "waive" | "require" | "add" | "ledger" | "loading" | "consumed" | "revoked" | "expired" | "active" | "local" | "parent" | "revoke">;
101
- network: AdminNetworkCopy;
102
- records: AdminRecordsCopy;
103
- };
104
- }
105
- type DeepPartial<T> = T extends readonly (infer Item)[] ? readonly Item[] : T extends object ? {
106
- [Key in keyof T]?: DeepPartial<T[Key]>;
107
- } : T;
108
- /** Recursively partial copy overrides accepted by `defineChapter({ copy })`. */
109
- type ChapterCopyInput = DeepPartial<ChapterCopy>;
110
-
111
- /** Explicit authority a chapter grants to one Built Not Found-style hub. */
112
- interface ChapterSignupControl {
113
- /** Stable app id expected in the signed delivery envelope. */
114
- sourceId: string;
115
- /** Follower-vault key holding the dedicated edge secret. */
116
- secretName?: string;
117
- /** Optional per-runtime vault keys. When present, unlisted runtimes and legacy untargeted deliveries fail closed. */
118
- runtimeSecrets?: Readonly<Record<string, string>>;
119
- /** Stripe mode this deployed environment accepts. */
120
- stripeMode: "test" | "live";
121
- }
122
- /** Validated signup-control authority carried on a resolved Chapter. */
123
- interface ResolvedSignupControl {
124
- sourceId: string;
125
- secretName: string;
126
- runtimeSecrets?: Readonly<Record<string, string>>;
127
- stripeMode: "test" | "live";
128
- }
129
-
130
- /** Optional authenticated member-content surface for a chapter site. */
131
- interface ChapterMemberContent {
132
- /** Keep Chapter-admin authoring enabled. Defaults to true. */
133
- localAuthoring?: boolean;
134
- /** Optional Parent/Built Not Found authority for signed inherited revisions. */
135
- parent?: {
136
- sourceId: string;
137
- /** Follower-vault key holding the dedicated content edge secret. */
138
- secretName?: string;
139
- };
140
- }
141
- /** Validated member-content settings carried on a resolved Chapter. */
142
- interface ResolvedMemberContent {
143
- enabled: true;
144
- localAuthoring: boolean;
145
- parent: {
146
- sourceId: string;
147
- secretName: string;
148
- } | null;
149
- }
150
-
151
- /** Optional private Builder Library shared across Built Not Found and Chapters. */
152
- interface ChapterBuilderLibrary {
153
- /** Keep local admin authoring enabled. Defaults to true. */
154
- localAuthoring?: boolean;
155
- /** Existing Chapter-network peers this app accepts. Hub sources must be
156
- * configured network targets; a Chapter source must be a configured reader. */
157
- sources?: readonly {
158
- sourceId: string;
159
- }[];
160
- }
161
- interface ResolvedBuilderLibrarySource {
162
- sourceId: string;
163
- }
164
- /** Validated Builder Library settings carried on a resolved Chapter. */
165
- interface ResolvedBuilderLibrary {
166
- enabled: true;
167
- localAuthoring: boolean;
168
- sources: readonly ResolvedBuilderLibrarySource[];
169
- }
170
-
171
- /** Opt-in network-scoped Person registry. Shadow mode never gates Chapter work. */
172
- interface ChapterNetworkPersonRegistry {
173
- mode: "shadow";
174
- /** Chapter ids that may be attached to imported source rows. Defaults to the
175
- * hub's configured network target ids. */
176
- chapterIds?: readonly string[];
177
- }
178
- /** Validated network Person registry settings carried by a resolved Chapter. */
179
- interface ResolvedNetworkPersonRegistry {
180
- mode: "shadow";
181
- chapterIds: readonly string[];
182
- }
183
-
184
- /** One exact Stripe event partition accepted by the network commercial shadow. */
185
- interface NetworkCommercialSource {
186
- appId: string;
187
- chapterId: string;
188
- environment: string;
189
- dataEnvironment: string;
190
- runtime: string;
191
- stripeMode: "test" | "live";
192
- }
193
- /** Opt-in, read-only projection of a shared Stripe account. */
194
- interface ChapterNetworkCommercialReadModel {
195
- mode: "shadow";
196
- sources: readonly NetworkCommercialSource[];
197
- }
198
- /** Validated settings carried by a resolved hub. */
199
- interface ResolvedNetworkCommercialReadModel {
200
- mode: "shadow";
201
- sources: readonly NetworkCommercialSource[];
202
- }
203
-
204
- /** Scalar kinds accepted by the Chapter schema adapter. */
205
- type AttrType = "string" | "number" | "boolean" | "json";
206
- /** One odla-db attribute: its type and index/uniqueness/optionality flags. */
207
- interface Attr {
208
- type: AttrType;
209
- unique: boolean;
210
- indexed: boolean;
211
- optional: boolean;
212
- }
213
- /** One odla-db namespace: its attribute map. */
214
- interface Entity {
215
- attrs: Record<string, Attr>;
216
- }
217
- /** A serialized odla-db schema fragment (namespaces + links). */
218
- interface DbSchema {
219
- entities: Record<string, Entity>;
220
- links: Record<string, unknown>;
221
- }
222
- /** Per-namespace CEL rule strings (deny-all is `"false"` for each action). */
223
- interface Rule {
224
- view: string;
225
- create: string;
226
- update: string;
227
- delete: string;
228
- }
229
- /** Namespace → rule set. */
230
- type DbRules = Record<string, Rule>;
231
-
232
- /** One follower site a leader can push CRM records to. The secret VALUE never
233
- * lives in config: `secretName` names the leader tenant's vaulted copy of the
234
- * follower's `network_share_secret`. Both slots are declared by
235
- * `createChapterIntegration` and filled together by
236
- * `odla-ai chapter edge pair --leader <leader> --follower <id>`; until then
237
- * every leader-side surface reports the edge as `edge_unpaired`. */
238
- interface ChapterNetworkTarget {
239
- /** Stable lowercase slug used by the UI, audit tags, and default secret name. */
240
- id: string;
241
- /** Human-facing site name. Defaults to `id`. */
242
- name?: string;
243
- /** Absolute follower origin, e.g. `https://chapter.example.com`. */
244
- url: string;
245
- /** Optional Cloudflare service-binding name for Worker-to-Worker delivery.
246
- * The URL still defines the signed request origin; the binding only supplies
247
- * the transport when a `workers.dev` subrequest cannot enter another Worker. */
248
- binding?: string;
249
- /** Leader-vault key holding this follower's share secret. Defaults to
250
- * `network_share_<id-with-underscores>`. */
251
- secretName?: string;
252
- /** Per-record-type field allowlist. If present, omitted types cannot be sent
253
- * to this target. Without it, package-safe person/company defaults apply. */
254
- fields?: Record<string, readonly string[]>;
255
- /** Record types on which leader admins may deliberately share notes with
256
- * this follower. Omitted by default: a target never gains write permission
257
- * merely because its records are browsable. */
258
- sharedNotes?: readonly string[];
259
- /** Per-record-type upper bound on profile fields this leader permits its
260
- * admins to edit remotely. The follower must independently delegate the
261
- * same field; readable fields never become writable implicitly. */
262
- editableFields?: Record<string, readonly string[]>;
263
- /** Record types on which this leader permits remote lifecycle transitions.
264
- * The follower must independently opt in and remains pipeline authority. */
265
- stageTransitions?: readonly string[];
266
- /** Allow package-owned desired admission decisions for existing leader CRM
267
- * people to be delivered to this target. Omitted means default deny. */
268
- admissionGrants?: boolean;
269
- /** Allow this leader to request a PII-free commercial parity snapshot from
270
- * this follower. The follower must independently enable the same reader;
271
- * omitted means no application or Stripe identifier digest crosses. */
272
- commercialParity?: boolean;
273
- }
274
- /** One signed peer allowed to browse a privacy-bounded projection of this
275
- * site's CRM. The follower owns this allowlist; a leader target declaration
276
- * cannot widen it. */
277
- interface ChapterNetworkReader {
278
- /** Stable app/site slug expected in the signed federation envelope. */
279
- id: string;
280
- /** Per-record-type field allowlist. Omitted types and fields never leave
281
- * this site. Record id, name, stage, and timestamps are bounded metadata. */
282
- fields?: Record<string, readonly string[]>;
283
- /** Record types on which this reader may append an explicitly shared note.
284
- * Omitted by default: browse permission never silently grants write access.
285
- * Shared notes land in the follower CRM activity feed. */
286
- sharedNotes?: readonly string[];
287
- /** Exact local CRM fields this reader may edit remotely, by record type.
288
- * Every field must also be readable so the peer can perform an optimistic
289
- * edit against the value and revision it actually reviewed. */
290
- editableFields?: Record<string, readonly string[]>;
291
- /** Local CRM record types on which this reader may request a lifecycle
292
- * transition. Chapter still enforces the local pipeline and gates. */
293
- stageTransitions?: readonly string[];
294
- /** Accept signed desired admission decisions from this reader. Independent
295
- * of record browsing; an admission-only reader may omit `fields`. */
296
- admissionGrants?: boolean;
297
- /** Disclose a bounded, PII-free commercial parity snapshot to this signed
298
- * reader. Identifiers leave only as edge-keyed, domain-separated HMACs. */
299
- commercialParity?: boolean;
300
- }
301
- /** Leader/follower network configuration. A follower needs no code config to
302
- * receive pushed records. It must explicitly declare `readers` before any peer
303
- * can browse its CRM. */
304
- interface ChapterNetwork {
305
- /** Require the protected super-admin tier for remote mutations and operational
306
- * detail. Ordinary admins retain routine name/email directory reads. */
307
- requireSuperAdminWrites?: boolean;
308
- targets?: readonly ChapterNetworkTarget[];
309
- readers?: readonly ChapterNetworkReader[];
310
- }
311
- /** Public chapter-formation intake backed by one CRM record type. The host owns
312
- * the branded form; Chapter owns the bounded, idempotent write contract. */
313
- interface LeaderFormationConfig {
314
- /** CRM type receiving submissions. Default `"chapter_application"`. */
315
- type?: string;
316
- /** Public fields that must be non-empty strings. */
317
- required?: readonly string[];
318
- /** Additional public fields accepted by the route. */
319
- optional?: readonly string[];
320
- maxLen?: Record<string, number>;
321
- /** Per-string cap when `maxLen` has no field override. Default 4000. */
322
- defaultMaxLen?: number;
323
- /** Maximum JSON request size in UTF-8 bytes, enforced while streaming. Default 32768. */
324
- bodyCap?: number;
325
- }
326
- /** Fully resolved formation intake policy. */
327
- interface ResolvedLeaderFormation {
328
- enabled: boolean;
329
- type: string;
330
- required: readonly string[];
331
- optional: readonly string[];
332
- maxLen: Record<string, number>;
333
- defaultMaxLen: number;
334
- bodyCap: number;
335
- }
336
- /** A validated network target carried on the resolved chapter engine. */
337
- interface ResolvedNetworkTarget {
338
- id: string;
339
- name: string;
340
- url: string;
341
- binding?: string;
342
- secretName: string;
343
- fields?: Record<string, readonly string[]>;
344
- sharedNotes: readonly string[];
345
- editableFields: Record<string, readonly string[]>;
346
- stageTransitions: readonly string[];
347
- admissionGrants?: boolean;
348
- commercialParity?: boolean;
349
- }
350
- /** A validated follower-owned record projection for one signed reader. */
351
- interface ResolvedNetworkReader {
352
- id: string;
353
- fields: Record<string, readonly string[]>;
354
- sharedNotes: readonly string[];
355
- editableFields: Record<string, readonly string[]>;
356
- stageTransitions: readonly string[];
357
- admissionGrants?: boolean;
358
- commercialParity?: boolean;
359
- }
360
- /** Fully-resolved leader network configuration. */
361
- interface ResolvedNetwork {
362
- requireSuperAdminWrites?: boolean;
363
- targets: readonly ResolvedNetworkTarget[];
364
- readers: readonly ResolvedNetworkReader[];
365
- }
366
-
367
- /**
368
- * Membership pricing for the group row (chapter mode).
369
- *
370
- * This is the single-price model that predates tiers. It still seeds the group
371
- * row and remains the fallback a chapter reads when it declares no tiers, so
372
- * existing chapters keep working; new work should prefer `tiers`.
373
- */
374
- interface ChapterPrices {
375
- standardCents: number;
376
- foundingDiscountCents?: number;
377
- /** ISO currency, default "usd". */
378
- currency?: string;
379
- /** Billing interval, default "year". */
380
- interval?: "year" | "month";
381
- }
382
- /** Membership policy + compliance copy stored on the group row. */
383
- interface ChapterPolicy {
384
- disclaimerText?: string;
385
- refundPolicyText?: string;
386
- /** Plain-language merchant identity shown immediately before payment consent. */
387
- merchantDisclosureText?: string;
388
- trustCopy?: string;
389
- commitmentText?: string;
390
- normsText?: string;
391
- }
392
-
393
- /** Which feature profile a site runs. `chapter` is the full public member site
394
- * (join, Stripe membership, booking, member area, admin, CRM); `hub` is
395
- * admin-only and CRM-focused (a directory/registry over the same CRM). */
396
- type ChapterMode = "chapter" | "hub";
397
- /** Typed semantic roles shared by the public site and application surfaces. */
398
- interface ChapterBrandTokens {
399
- background?: string;
400
- surface?: string;
401
- surface2?: string;
402
- text?: string;
403
- textMuted?: string;
404
- textFaint?: string;
405
- border?: string;
406
- borderStrong?: string;
407
- accent?: string;
408
- accentStrong?: string;
409
- accentSoft?: string;
410
- onAccent?: string;
411
- good?: string;
412
- warn?: string;
413
- danger?: string;
414
- chart1?: string;
415
- chart2?: string;
416
- chart3?: string;
417
- chart4?: string;
418
- chart5?: string;
419
- chart6?: string;
420
- chartPositive?: string;
421
- chartNegative?: string;
422
- /** Admin/application content width, e.g. `"960px"`. */
423
- contentWidth?: string;
424
- panelRadius?: string;
425
- panelShadow?: string;
426
- masterDetailColumns?: string;
427
- masterDetailMinHeight?: string;
428
- /** Chapter-admin page padding shorthand. */
429
- pagePadding?: string;
430
- /** Chapter-admin workspace-tab padding shorthand. */
431
- workspacePadding?: string;
432
- }
433
- /** Brand tokens that theme the site: theme, semantic roles, fonts, and logos. */
434
- interface ChapterBrand {
435
- /** Preloaded @odla-ai/ui theme name used by ThemeScope. */
436
- theme?: string;
437
- /** Named accent family supplied by the selected @odla-ai/ui theme. */
438
- accent?: string;
439
- /** Admin/application color mode. Explicit light is the safe default. */
440
- colorScheme?: "light" | "dark" | "system";
441
- tokens?: ChapterBrandTokens;
442
- tokensDark?: ChapterBrandTokens;
443
- /** Palette overrides written into styles.css :root (e.g. `{ moss: "#2F3E34" }`
444
- * or `--ui-*` token names). Applies in BOTH light and dark unless overridden
445
- * by {@link paletteDark}. */
446
- palette?: Record<string, string>;
447
- /** Dark-mode palette overrides. Same shape as {@link palette}; emitted under
448
- * `:root[data-theme="dark"]` and `@media (prefers-color-scheme: dark)`, so a
449
- * site brands both modes. */
450
- paletteDark?: Record<string, string>;
451
- /** Full token map for `.ui-invert` islands. A compiled custom theme normally
452
- * uses its dark map here so inverted content never inherits stale composites. */
453
- paletteInvert?: Record<string, string>;
454
- fonts?: {
455
- display?: string;
456
- body?: string;
457
- numeral?: string;
458
- };
459
- /** Short glyph used in compact admin chrome. Defaults to the first three
460
- * letters of the chapter name. */
461
- badge?: string;
462
- wordmark?: string;
463
- tagline?: string;
464
- /** Header/footer nav sections for the web-component chrome: label → entries. */
465
- nav?: Record<string, ReadonlyArray<{
466
- label: string;
467
- href: string;
468
- }>>;
469
- logos?: string;
470
- }
471
-
472
- /** One owner-editable transactional email template. Automatic sends require explicit `enabled: true`. */
473
- interface EmailTemplate {
474
- subject: string;
475
- text: string;
476
- enabled?: boolean;
477
- }
478
- /** Notification/reply/debug addresses + operational email templates. */
479
- interface ChapterEmails {
480
- notificationEmail: string;
481
- replyTo?: string;
482
- /** Display name on outbound lifecycle mail (`Tori Horton <from@…>`); the
483
- * address itself stays the Worker's `EMAIL_FROM`. Absent ⇒ bare address. */
484
- fromName?: string;
485
- debugEmail?: string;
486
- /** Operational lifecycle templates: adminNotification / submitConfirmation /
487
- * paymentConfirmation / prepEmail / onboardingInvite / namedSeatInvitation / seatAccepted,
488
- * plus declared host events. Missing enabled flags block sending. */
489
- templates?: Record<string, EmailTemplate>;
490
- /** Additional host-owned email events shown and validated in the email editor. Declaring an event never sends it. */
491
- events?: Record<string, ChapterEmailEvent>;
492
- }
493
- /** Booking rules for the intro-call scheduler (stored as schedulingJson). */
494
- interface ChapterScheduling {
495
- slotMinutes?: number;
496
- days?: readonly number[];
497
- startHour?: number;
498
- endHour?: number;
499
- timezone?: string;
500
- minNoticeHours?: number;
501
- windowDays?: number;
502
- summaryTemplate?: string;
503
- }
504
- /** How a signed-in user's role is determined.
505
- *
506
- * - `"clerk"` reads backend-only private Clerk roles for each authorization
507
- * check, without trusting public metadata or stale session roles.
508
- * - `"claim"` reads a role ladder from a JWT claim (the legacy member-site model:
509
- * `provisional → member → admin`). Admin is the last (highest) rung; the
510
- * Studio-managed super-admin tier sits above it.
511
- * - `"table"` gates on the deny-all `admins` allowlist (the hub model):
512
- * a binary "email is an admin or isn't", set only in odla Studio.
513
- *
514
- * Defaults: `"claim"` in `chapter` mode, `"table"` in `hub` mode. */
515
- interface ChapterAuth {
516
- /** `clerk` reads backend-only private_metadata.role on each request, ignoring
517
- * JWT/public roles. Select explicitly after the coordinated role migration. */
518
- source?: "claim" | "table" | "clerk";
519
- /** JWT claim holding the role, for `source: "claim"`. Default `"role"`. */
520
- claim?: string;
521
- /** Role ladder low→high, for claim or private Clerk roles. The last entry is admin.
522
- * Default `["provisional", "member", "admin"]`. */
523
- ladder?: readonly string[];
524
- /** Enable the Studio-managed super-admin tier — the only tier that may
525
- * create/modify admins. Default: `true` for `"claim"`/`"clerk"`, `false` for `"table"`. */
526
- superAdmins?: boolean;
527
- /** Authority behind the super-admin tier. `"table"` preserves the legacy
528
- * app allowlist; `"odla"` reads protected `$users.role` by exact subject ID.
529
- * Default `"odla"` for private Clerk mode, `"table"` for legacy modes. */
530
- superAdminSource?: "table" | "odla";
531
- }
532
- /** The fully-resolved auth policy (defaults applied) carried on the {@link Chapter}. */
533
- interface ResolvedAuth {
534
- source: "claim" | "table" | "clerk";
535
- claim: string;
536
- ladder: readonly string[];
537
- /** The highest ladder rung (last entry) — the "admin" gate. */
538
- adminRole: string;
539
- superAdmins: boolean;
540
- superAdminSource: "none" | "table" | "odla";
541
- }
542
- /** The application status pipeline. Which statuses exist, and the subsets a site
543
- * allows a call to be booked from / an application approved from. Defaults to
544
- * the reference pipeline. Status never moves backwards (package-enforced). */
545
- interface ChapterPipeline {
546
- stages?: readonly string[];
547
- bookableFrom?: readonly string[];
548
- approvableFrom?: readonly string[];
549
- /** The status a new application starts at. Default: the first stage. */
550
- initial?: string;
551
- }
552
- /** The fully-resolved pipeline (defaults applied) carried on the {@link Chapter}. */
553
- interface ResolvedPipeline {
554
- stages: readonly string[];
555
- bookableFrom: readonly string[];
556
- approvableFrom: readonly string[];
557
- initial: string;
558
- }
559
- /** The application (join form) validation surface — which string fields are
560
- * required vs accepted, their max lengths, and the request body cap. Drives
561
- * submit validation, the generated `applications` schema, and the CRM slot
562
- * projection; defaults to the reference form. Built-in fields retain their
563
- * declared schema types/indexes, and site-defined fields become string attrs. */
564
- interface ChapterApplication {
565
- required?: readonly string[];
566
- optional?: readonly string[];
567
- /** Per-field CEL conditions — show a field, or demand it, based on the
568
- * answers so far. Enforced server-side as well as rendered, so a hidden
569
- * field is never demanded and a conditionally-required one cannot be
570
- * skipped by posting directly. Validated when the chapter is defined. */
571
- conditions?: Record<string, FieldConditions>;
572
- /** Per-field character cap. Fields not listed use `defaultMaxLen`. */
573
- maxLen?: Record<string, number>;
574
- defaultMaxLen?: number;
575
- /** Max JSON request body in UTF-8 bytes, enforced while streaming. Default 32768. */
576
- bodyCap?: number;
577
- /** Reject a submit that carries no truthy `disclaimerAck` (400), instead of
578
- * writing a row with no consent record. Default `true`. Set `false`
579
- * deliberately only when the site renders no consent control. */
580
- requireDisclaimerAck?: boolean;
581
- /** Allowlist of application fields mirrored into the Clerk account's
582
- * backend-only `private_metadata.profile`. Default `[]`. Use this for small
583
- * account/admin signals; the application row and CRM remain canonical. */
584
- profileFields?: readonly string[];
585
- /** Extra application fields carried into the one-way CRM projection, on top of
586
- * the built-in identity/contact set. Each MUST be declared on your crm person
587
- * type or the enrichment is dropped (the base person still projects). Default
588
- * none. */
589
- crmFields?: readonly string[];
590
- /** Cap on the element count of array-valued fields (e.g. `focus`), so a client
591
- * cannot post a 10k-element array into a row or into Clerk metadata.
592
- * Non-primitive elements are dropped. Default 100. */
593
- maxArrayLen?: number;
594
- /** Validate that a field literally named `email` looks like an email address,
595
- * returning a 400 rather than accepting input the downstream Clerk create will
596
- * reject anyway. Default `true`; a valid application is never newly rejected. */
597
- validateEmail?: boolean;
598
- }
599
- /** The fully-resolved application config carried on the {@link Chapter}. */
600
- interface ResolvedApplication {
601
- required: readonly string[];
602
- optional: readonly string[];
603
- maxLen: Record<string, number>;
604
- defaultMaxLen: number;
605
- bodyCap: number;
606
- requireDisclaimerAck: boolean;
607
- /** Resolved Clerk-metadata allowlist. Empty means no profile projection. */
608
- profileFields: readonly string[];
609
- crmFields: readonly string[];
610
- maxArrayLen: number;
611
- validateEmail: boolean;
612
- /** Resolved per-field conditions. Empty means every field is unconditional. */
613
- conditions: Record<string, FieldConditions>;
614
- }
615
- /** The `defineChapter()` config a site fills in. */
616
- interface ChapterConfig {
617
- /** Slug: app id, tenant, group id, worker name. `[a-z0-9-]`. */
618
- id: string;
619
- name: string;
620
- url?: string;
621
- /** Default `"chapter"`. */
622
- mode?: ChapterMode;
623
- /** A `defineCrm()` config or a resolved `Crm`. Omit for the per-mode default. */
624
- crm?: CrmConfig | Crm;
625
- brand?: ChapterBrand;
626
- /** User-visible language for packaged Chapter surfaces. Recursively partial;
627
- * `defineChapter()` resolves every omitted leaf from the package defaults. */
628
- copy?: ChapterCopyInput;
629
- /** Signed network edges: leader delivery targets and follower-owned,
630
- * privacy-bounded record readers. Secret values stay in tenant vaults. */
631
- network?: ChapterNetwork;
632
- /** A separate signed edge that may control this chapter's complete signup. */
633
- signupControl?: ChapterSignupControl;
634
- /** Authenticated parent + chapter member pages with server-enforced tier access. */
635
- memberContent?: ChapterMemberContent;
636
- /** Private docs, Brand asset references, and links for authorized site-building agents. */
637
- builderLibrary?: ChapterBuilderLibrary;
638
- /** Network-scoped canonical Person registry. Shadow mode is additive only:
639
- * Chapter workflows and access continue to read their existing authority. */
640
- networkPersonRegistry?: ChapterNetworkPersonRegistry;
641
- /** Read-only, rebuildable commercial projection from a shared Stripe account. */
642
- networkCommercialReadModel?: ChapterNetworkCommercialReadModel;
643
- /** Enable a public, host-rendered formation application workflow. */
644
- formation?: LeaderFormationConfig;
645
- thesis?: unknown;
646
- /** Required in `chapter` mode, and the fallback when `tiers` is empty. */
647
- prices?: ChapterPrices;
648
- /** Membership tiers. A tier priced at 0 is free and never touches Stripe. */
649
- tiers?: ChapterTierConfig[];
650
- policy?: ChapterPolicy;
651
- /** `notificationEmail` required in `chapter` mode. */
652
- emails?: ChapterEmails;
653
- scheduling?: ChapterScheduling;
654
- /** Application status pipeline (stages + bookable/approvable subsets). */
655
- pipeline?: ChapterPipeline;
656
- /** Join-form validation (required/optional fields, max lengths, body cap). */
657
- application?: ChapterApplication;
658
- /** Role source + ladder + super-admin tier. Defaults by mode (see {@link ChapterAuth}). */
659
- auth?: ChapterAuth;
660
- /** odla services (db implied). Default `["db","calendar","o11y"]`. */
661
- services?: readonly string[];
662
- /** Apply-time account provisioning. Required in `chapter` mode so a site must
663
- * make the decision explicitly. `"none"` provisions nothing, `"create"` makes
664
- * the Clerk account server-side, and `"invite"` emails a real invitation.
665
- * Both side-effecting models need a `clerk_secret_key` vault secret. */
666
- account?: AccountModel;
667
- /** WHEN lifecycle email fires. Addressing and content live on the group row
668
- * (owner-editable at runtime); this is the trigger, which is a build-time
669
- * decision. See {@link ChapterSends}. */
670
- sends?: ChapterSends;
671
- /** Site policy for admin operations (approve side effects, refund rules).
672
- * See {@link ChapterOperations}. */
673
- operations?: ChapterOperations;
674
- }
675
- /** Apply-time Clerk account provisioning model. */
676
- type AccountModel = "invite" | "create" | "none";
677
- /** Site policy for admin OPERATIONS: the DECISIONS, where the mechanics stay
678
- * package-owned. Same model as {@link ChapterSends} — a site declares the rule,
679
- * chapter enforces it. Distinct from {@link ChapterPolicy}, which is member-facing
680
- * copy. (The privilege-escalation rules are NOT here: they are package-enforced
681
- * in `canChangeRole`, gated on `auth.superAdmins`, so a site cannot weaken them.) */
682
- interface ChapterOperations {
683
- /** What approving an application does. */
684
- onApprove?: {
685
- /** Role to promote the applicant to in Clerk. Defaults to the ladder rung
686
- * directly below admin (e.g. `"member"`); `false` promotes nobody. */
687
- promoteTo?: string | false;
688
- /** Group email template to send on approve. Default `"onboardingInvite"`;
689
- * `false` sends nothing. */
690
- send?: string | false;
691
- };
692
- /** Refund rules. */
693
- refund?: {
694
- /** Application statuses a refund may be issued from. Default: any status. */
695
- allowedFrom?: readonly string[];
696
- /** Also cancel the Stripe subscription. Default `true`. */
697
- cancelSubscription?: boolean;
698
- };
699
- }
700
- /** The fully-resolved {@link ChapterOperations} carried on the {@link Chapter}. */
701
- interface ResolvedOperations {
702
- onApprove: {
703
- promoteTo: string | false;
704
- send: string | false;
705
- };
706
- /** `allowedFrom: null` means "any status". */
707
- refund: {
708
- allowedFrom: readonly string[] | null;
709
- cancelSubscription: boolean;
710
- };
711
- }
712
- /** When the admin notification fires: on application `submit` (default), on the
713
- * first successful `payment`, or `never` (the site drives it itself). */
714
- type AdminNotificationTrigger = "submit" | "payment" | "never";
715
- /** Send-policy config: the trigger for each lifecycle email chapter owns. */
716
- interface ChapterSends {
717
- adminNotification?: AdminNotificationTrigger;
718
- }
719
- /** Resolved send policy (every trigger present). */
720
- interface ResolvedSends {
721
- adminNotification: AdminNotificationTrigger;
722
- }
723
- /** The resolved engine `defineChapter()` returns. */
724
- interface Chapter {
725
- config: ChapterConfig;
726
- id: string;
727
- name: string;
728
- url?: string;
729
- mode: ChapterMode;
730
- /** Resolved site identity. `wordmark` always falls back to `name`, so the
731
- * admin/member UI never needs a second brand declaration. */
732
- brand: ChapterBrand;
733
- /** Fully resolved user-visible text for packaged Chapter surfaces. */
734
- copy: ChapterCopy;
735
- /** Validated follower targets for leader-driven record pushes. */
736
- network: ResolvedNetwork;
737
- /** Null unless this follower explicitly delegates signup authority. */
738
- signupControl: ResolvedSignupControl | null;
739
- /** Null unless the standard authenticated member-content surface is enabled. */
740
- memberContent: ResolvedMemberContent | null;
741
- /** Null unless the private scoped Builder Library is enabled. */
742
- builderLibrary: ResolvedBuilderLibrary | null;
743
- /** Null unless this hub explicitly enables the shadow Person registry. */
744
- networkPersonRegistry: ResolvedNetworkPersonRegistry | null;
745
- /** Null unless this hub explicitly enables the commercial shadow. */
746
- networkCommercialReadModel: ResolvedNetworkCommercialReadModel | null;
747
- /** Resolved public formation intake contract (disabled when omitted). */
748
- formation: ResolvedLeaderFormation;
749
- /** Resolved CRM engine (from `defineCrm`). */
750
- crm: Crm;
751
- /** Resolved auth policy (source, claim, ladder, super-admin tier). */
752
- auth: ResolvedAuth;
753
- /** Resolved application status pipeline (stages + bookable/approvable subsets). */
754
- pipeline: ResolvedPipeline;
755
- /** Resolved join-form validation config. */
756
- application: ResolvedApplication;
757
- /** The chapter's own odla-db namespaces (mode-dependent; excludes `crm_*`). */
758
- schema: DbSchema;
759
- rules: DbRules;
760
- services: readonly string[];
761
- /** Resolved apply-time account provisioning model. */
762
- account: AccountModel;
763
- /** Resolved send policy — when each lifecycle email fires. */
764
- sends: ResolvedSends;
765
- /** Resolved admin-operation policy (approve side effects, refund rules). */
766
- operations: ResolvedOperations;
767
- /** The seed `groups` row derived from config (chapter mode), else `null`. */
768
- groupSeed(): Record<string, unknown> | null;
769
- }
770
-
771
- /** Versioned contract for authenticated member content. */
772
- declare const MEMBER_CONTENT_SCHEMA_VERSION: 1;
773
- /** Authority that authored one independently stored member-content layer. */
774
- type MemberContentSourceType = "parent" | "chapter";
775
- /** Stable author identity attached to every member-content document and page. */
776
- interface MemberContentSource {
777
- type: MemberContentSourceType;
778
- appId: string;
779
- }
780
- /** Chapter deployment that a parent member-content revision is intended for. */
781
- interface MemberContentTarget {
782
- chapterId: string;
783
- environment: string;
784
- }
785
- /** Navigation is data, so agents do not have to coordinate route code. */
786
- interface MemberPageNavigation {
787
- section: string;
788
- order: number;
789
- parentId?: string;
790
- }
791
- /** Explicit sets avoid unsafe assumptions that commercial tiers form a ladder. */
792
- type MemberPageAudience = {
793
- kind: "all-members";
794
- } | {
795
- kind: "tiers";
796
- tierIds: readonly string[];
797
- };
798
- /** Authored offer copy. Price and provider identity deliberately do not live here. */
799
- interface MemberPageUpsellOffer {
800
- tierId: string;
801
- label?: string;
802
- bodyMarkdown?: string;
803
- ctaLabel?: string;
804
- }
805
- /** Parent offer authority is explicit; a Chapter can edit only a delegated offer. */
806
- type MemberPageUpsell = {
807
- owner: "parent";
808
- offer: MemberPageUpsellOffer;
809
- } | {
810
- owner: "chapter";
811
- offer?: MemberPageUpsellOffer;
812
- };
813
- /** Authored member page whose body is disclosed only after server-side projection. */
814
- interface MemberContentPage {
815
- id: string;
816
- slug: string;
817
- title: string;
818
- summary?: string;
819
- navigation: MemberPageNavigation;
820
- audience: MemberPageAudience;
821
- /** Safe Markdown shown while the body is withheld. */
822
- teaserMarkdown?: string;
823
- /** Never serialized to a viewer whose audience does not match. */
824
- bodyMarkdown: string;
825
- upsell?: MemberPageUpsell;
826
- }
827
- /** Chapter-owned offer attached to a Parent page that delegated offer authority. */
828
- interface MemberPageUpsellOverride {
829
- pageId: string;
830
- offer: MemberPageUpsellOffer;
831
- }
832
- /** One complete source layer. Parent and Chapter layers are stored independently. */
833
- interface MemberContentDocument {
834
- schemaVersion: typeof MEMBER_CONTENT_SCHEMA_VERSION;
835
- revision: number;
836
- generatedAt: number;
837
- source: MemberContentSource;
838
- target?: MemberContentTarget;
839
- pages: readonly MemberContentPage[];
840
- upsellOverrides?: readonly MemberPageUpsellOverride[];
841
- }
842
- /** Provider-neutral tier facts used to validate access and resolve safe offers. */
843
- interface MemberContentTierAuthority {
844
- id: string;
845
- name?: string;
846
- priceCents?: number;
847
- active?: boolean;
848
- }
849
- /** Composed member page retaining the identity of its authoring layer. */
850
- interface EffectiveMemberContentPage extends MemberContentPage {
851
- source: MemberContentSource;
852
- }
853
- /** Deterministically ordered parent and chapter layers with their source revisions. */
854
- interface EffectiveMemberContent {
855
- pages: readonly EffectiveMemberContentPage[];
856
- parentRevision: number | null;
857
- chapterRevision: number | null;
858
- }
859
- /** Authored upsell joined to active chapter tier name and price authority. */
860
- interface ResolvedMemberPageUpsell extends MemberPageUpsellOffer {
861
- tierName: string;
862
- priceCents: number;
863
- }
864
- /** Viewer-safe page result; locked projections never carry bodyMarkdown. */
865
- interface MemberContentPageProjection {
866
- id: string;
867
- slug: string;
868
- title: string;
869
- summary?: string;
870
- navigation: MemberPageNavigation;
871
- audience: MemberPageAudience;
872
- source: MemberContentSource;
873
- access: "allowed" | "locked";
874
- teaserMarkdown?: string;
875
- bodyMarkdown?: string;
876
- upsell?: ResolvedMemberPageUpsell;
877
- }
878
- /** Complete viewer-safe member navigation and revision projection. */
879
- interface MemberContentProjection {
880
- pages: readonly MemberContentPageProjection[];
881
- viewer: {
882
- tierId: string | null;
883
- admin: boolean;
884
- };
885
- revisions: {
886
- parent: number | null;
887
- chapter: number | null;
888
- };
889
- }
890
-
891
- /** Place a resolved or partial copy contract around any Chapter UI surface. */
892
- declare function ChapterCopyProvider(props: {
893
- copy?: ChapterCopy | ChapterCopyInput;
894
- children: ComponentChildren;
895
- }): preact.JSX.Element;
896
- /** Read the nearest complete Chapter copy contract. */
897
- declare function useChapterCopy(): ChapterCopy;
898
-
899
- export { ChapterCopyProvider as C, type EffectiveMemberContent as E, type MemberContentDocument as M, type Chapter as a, type ChapterCopy as b, type MemberContentTierAuthority as c, type MemberContentProjection as d, type MemberContentPageProjection as e, type ChapterBrand as f, useChapterCopy as u };