@odla-ai/chapter 0.22.1 → 0.23.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/dist/index.d.cts CHANGED
@@ -1,5 +1,57 @@
1
1
  import { CrmConfig, Crm, CrmRecord } from '@odla-ai/crm';
2
2
 
3
+ /** Complete default language resolved beneath partial Chapter copy overrides. */
4
+ declare const DEFAULT_CHAPTER_COPY: ChapterCopy;
5
+ /** Resolve a partial copy contract and validate every supplied leaf. */
6
+ declare function resolveChapterCopy(input?: ChapterCopyInput): ChapterCopy;
7
+ /** Replace `{name}` placeholders without interpreting the resulting text as HTML. */
8
+ declare function formatChapterCopy(template: string, values: Readonly<Record<string, string | number>>): string;
9
+
10
+ type TextFields<Key extends string> = {
11
+ [Field in Key]: string;
12
+ };
13
+ /**
14
+ * Complete user-visible language owned by Chapter's packaged surfaces.
15
+ *
16
+ * Copy is serializable text; use render slots when presentation needs markup and
17
+ * `formatChapterCopy()` for typed placeholders.
18
+ */
19
+ interface ChapterCopy {
20
+ common: TextFields<"loading" | "saved" | "unknown">;
21
+ join: {
22
+ form: TextFields<"submit" | "submitting" | "submitFailed" | "unexpectedFailure">;
23
+ booking: TextFields<"unavailable" | "loadFailed" | "slotTaken" | "failed" | "loading" | "book" | "booking">;
24
+ payment: TextFields<"setupFailed" | "preparing" | "pending" | "processing" | "payAndContinue" | "incomplete">;
25
+ done: TextFields<"label" | "calendarInvite" | "memberArea">;
26
+ };
27
+ members: {
28
+ loadFailed: string;
29
+ account: TextFields<"signOut" | "adminConsole">;
30
+ provisional: TextFields<"cardLabel" | "applicationNeeded" | "applicationNeededBody" | "apply" | "refunded" | "refundedBody" | "active" | "renews" | "introductionCall" | "calendarInvite" | "joinCall" | "bookCall" | "bookCallBody" | "chooseTime">;
31
+ full: TextFields<"welcome">;
32
+ reschedule: TextFields<"changeTime" | "unavailable" | "loadFailed" | "slotGone" | "loading" | "noTimes" | "rescheduling" | "keepTime">;
33
+ };
34
+ admin: {
35
+ auth: TextFields<"checking" | "notAuthorized" | "accountNotAuthorized" | "thisAccount" | "signOut" | "loading" | "signInNotConfigured" | "missingPublishableKey" | "noWorkspaces" | "signInTagline">;
36
+ shell: TextFields<"adminRole" | "adminConsole" | "adminName" | "navigationLabel">;
37
+ workspaces: TextFields<"dashboard" | "overview" | "billing" | "people" | "settings" | "calendar" | "email" | "dashboardViewsLabel" | "settingsViewsLabel" | "collectionsLabel">;
38
+ dashboard: TextFields<"loading" | "loadFailed" | "upcomingCalls" | "callsNeedAttention" | "noUpcomingCalls" | "drift" | "open" | "meet" | "applications" | "newMembers" | "newMembersUnavailable" | "revenueAdded" | "revenue" | "revenueUnavailable" | "pipeline" | "pipelineLabel" | "thisWeek" | "noChange" | "activeMemberships" | "annualRunRate" | "testMode">;
39
+ billing: TextFields<"loading" | "loadFailed" | "notConfigured" | "active" | "annualized" | "renewingSoon" | "pastDue" | "subscriptions" | "testMode" | "truncated" | "name" | "email" | "application" | "subscription" | "cancelling" | "amount" | "renews">;
40
+ availability: TextFields<"loading" | "loadFailed" | "saved" | "title" | "days" | "startHour" | "endHour" | "slotMinutes" | "minNoticeHours" | "windowDays" | "timezone" | "summaryTemplate" | "save"> & {
41
+ dayLabels: readonly string[];
42
+ };
43
+ email: TextFields<"loading" | "loadFailed" | "saved" | "sent" | "testFailed" | "delivery" | "notificationAddress" | "replyTo" | "debugInbox" | "debugInboxHint" | "sendTest" | "enabled" | "subject" | "body" | "save" | "sendLog" | "sentColumn" | "templateColumn" | "statusColumn" | "toColumn" | "failed" | "redirected" | "delivered">;
44
+ meetings: TextFields<"loading" | "loadFailed" | "cancelConfirm" | "newStartPrompt" | "parseFailed" | "agenda" | "empty" | "unknown" | "drift" | "meet" | "reschedule" | "cancel">;
45
+ network: TextFields<"title" | "allowlistDescription" | "sharing" | "shareWith" | "shared" | "shareFailed" | "deliveryFailed">;
46
+ records: TextFields<"workflowMissing" | "comms" | "commsHistory" | "scheduling" | "billing" | "notes" | "access" | "sharing" | "lifecycle" | "role" | "superAdminHint" | "noAccount" | "approve" | "refund" | "approved" | "refunded" | "roleSet" | "messageSent" | "messageNotSent" | "loadingTemplates" | "template" | "chooseTemplate" | "sending" | "send" | "noMessages" | "message" | "newStartPrompt" | "parseFailed" | "cancelConfirm" | "callCancelled" | "callRescheduled" | "noApplication" | "loadingMeetings" | "noMeetings" | "joinMeeting" | "openCalendar" | "reschedule" | "cancel">;
47
+ };
48
+ }
49
+ type DeepPartial<T> = T extends readonly (infer Item)[] ? readonly Item[] : T extends object ? {
50
+ [Key in keyof T]?: DeepPartial<T[Key]>;
51
+ } : T;
52
+ /** Recursively partial copy overrides accepted by `defineChapter({ copy })`. */
53
+ type ChapterCopyInput = DeepPartial<ChapterCopy>;
54
+
3
55
  /** Which feature profile a site runs. `chapter` is the full public member site
4
56
  * (join, Stripe membership, booking, member area, admin, CRM); `hub` is
5
57
  * admin-only and CRM-focused (a directory/registry over the same CRM). */
@@ -93,6 +145,8 @@ interface ChapterBrandTokens {
93
145
  interface ChapterBrand {
94
146
  /** Preloaded @odla-ai/ui theme name used by ThemeScope. */
95
147
  theme?: string;
148
+ /** Named accent family supplied by the selected @odla-ai/ui theme. */
149
+ accent?: string;
96
150
  /** Admin/application color mode. Explicit light is the safe default. */
97
151
  colorScheme?: "light" | "dark" | "system";
98
152
  tokens?: ChapterBrandTokens;
@@ -105,6 +159,9 @@ interface ChapterBrand {
105
159
  * `:root[data-theme="dark"]` and `@media (prefers-color-scheme: dark)`, so a
106
160
  * site brands both modes. */
107
161
  paletteDark?: Record<string, string>;
162
+ /** Full token map for `.ui-invert` islands. A compiled custom theme normally
163
+ * uses its dark map here so inverted content never inherits stale composites. */
164
+ paletteInvert?: Record<string, string>;
108
165
  fonts?: {
109
166
  display?: string;
110
167
  body?: string;
@@ -260,17 +317,13 @@ interface ChapterApplication {
260
317
  /** Max JSON request body in bytes. Default 32768. */
261
318
  bodyCap?: number;
262
319
  /** Reject a submit that carries no truthy `disclaimerAck` (400), instead of
263
- * writing a row with no consent record. Default `false` for back-compat —
264
- * but turn it on if the disclaimer is a compliance record: a missing ack is
265
- * otherwise silent, permanent and unreconstructible. Failure is deterministic
266
- * and surfaces on the first test submit, not intermittently in production. */
320
+ * writing a row with no consent record. Default `true`. Set `false`
321
+ * deliberately only when the site renders no consent control. */
267
322
  requireDisclaimerAck?: boolean;
268
323
  /** Allowlist of fields that reach the Clerk account's client-readable
269
- * `public_metadata.profile`. Default (unset) projects every non-identity
270
- * configured field convenient, but it also exposes free-text and
271
- * third-party fields (`message`, `referral`). Set this to a curated list
272
- * (e.g. `["phone", "state", "focus"]`) to keep confidential fields db-only.
273
- * Expected to become required-in-spirit at 1.0. */
324
+ * `public_metadata.profile`. Default `[]`, so application details remain
325
+ * db-only. Set a curated list (e.g. `["phone", "state", "focus"]`) for
326
+ * fields the browser may read. */
274
327
  profileFields?: readonly string[];
275
328
  /** Extra application fields carried into the one-way CRM projection, on top of
276
329
  * the built-in identity/contact set. Each MUST be declared on your crm person
@@ -294,8 +347,8 @@ interface ResolvedApplication {
294
347
  defaultMaxLen: number;
295
348
  bodyCap: number;
296
349
  requireDisclaimerAck: boolean;
297
- /** Resolved Clerk-metadata allowlist; `null` means "all non-identity fields". */
298
- profileFields: readonly string[] | null;
350
+ /** Resolved Clerk-metadata allowlist. Empty means no profile projection. */
351
+ profileFields: readonly string[];
299
352
  crmFields: readonly string[];
300
353
  maxArrayLen: number;
301
354
  validateEmail: boolean;
@@ -311,6 +364,9 @@ interface ChapterConfig {
311
364
  /** A `defineCrm()` config or a resolved `Crm`. Omit for the per-mode default. */
312
365
  crm?: CrmConfig | Crm;
313
366
  brand?: ChapterBrand;
367
+ /** User-visible language for packaged Chapter surfaces. Recursively partial;
368
+ * `defineChapter()` resolves every omitted leaf from the package defaults. */
369
+ copy?: ChapterCopyInput;
314
370
  /** Leader → follower delivery targets. Secret values stay in the leader
315
371
  * tenant vault; see {@link ChapterNetworkTarget}. */
316
372
  network?: ChapterNetwork;
@@ -329,13 +385,10 @@ interface ChapterConfig {
329
385
  auth?: ChapterAuth;
330
386
  /** odla services (db implied). Default `["db","calendar","o11y"]`. */
331
387
  services?: readonly string[];
332
- /** Apply-time account provisioning. **Default `"none"`** it provisions
333
- * nothing, because the alternatives have an outbound side effect and a site
334
- * that never made the choice must not be mailing people. `"create"` makes the
335
- * Clerk account server-side (so join can say the account is ready);
336
- * `"invite"` **emails the applicant a Clerk invitation**. Both non-default
337
- * models need a `clerk_secret_key` vault secret to act. Opt in explicitly —
338
- * leaving this unset provisions no accounts. */
388
+ /** Apply-time account provisioning. Required in `chapter` mode so a site must
389
+ * make the decision explicitly. `"none"` provisions nothing, `"create"` makes
390
+ * the Clerk account server-side, and `"invite"` emails a real invitation.
391
+ * Both side-effecting models need a `clerk_secret_key` vault secret. */
339
392
  account?: AccountModel;
340
393
  /** WHEN lifecycle email fires. Addressing and content live on the group row
341
394
  * (owner-editable at runtime); this is the trigger, which is a build-time
@@ -403,6 +456,8 @@ interface Chapter {
403
456
  /** Resolved site identity. `wordmark` always falls back to `name`, so the
404
457
  * admin/member UI never needs a second brand declaration. */
405
458
  brand: ChapterBrand;
459
+ /** Fully resolved user-visible text for packaged Chapter surfaces. */
460
+ copy: ChapterCopy;
406
461
  /** Validated follower targets for leader-driven record pushes. */
407
462
  network: ResolvedNetwork;
408
463
  /** Resolved CRM engine (from `defineCrm`). */
@@ -417,7 +472,7 @@ interface Chapter {
417
472
  schema: DbSchema;
418
473
  rules: DbRules;
419
474
  services: readonly string[];
420
- /** Resolved apply-time account provisioning model (default `"none"`). */
475
+ /** Resolved apply-time account provisioning model. */
421
476
  account: AccountModel;
422
477
  /** Resolved send policy — when each lifecycle email fires. */
423
478
  sends: ResolvedSends;
@@ -803,10 +858,10 @@ declare function hasDisclaimerAck(fields: Record<string, unknown>): boolean;
803
858
  /**
804
859
  * The applicant profile written to the Clerk account's client-readable
805
860
  * `public_metadata.profile`. Projects each configured non-identity field, plus
806
- * `focus` (clamped) but ONLY those in `application.profileFields` when that
807
- * allowlist is set, so a site keeps confidential fields (`message`, `referral`)
808
- * db-only. Derived from config, so a site's own field names project without this
809
- * package knowing them. Pure; returns `undefined` when there is nothing to write.
861
+ * `focus` (clamped), but ONLY those in `application.profileFields`, so a site
862
+ * keeps confidential fields (`message`, `referral`) db-only. Derived from
863
+ * config, so a site's own field names project without this package knowing
864
+ * them. Pure; returns `undefined` when there is nothing to write.
810
865
  */
811
866
  declare function applicantProfile(chapter: Chapter, fields: Record<string, unknown>): Record<string, unknown> | undefined;
812
867
  /** A validated submission, or a 400-worthy validation error the route returns.
@@ -1243,6 +1298,17 @@ declare function memberSession(user: SessionUser, opts: {
1243
1298
  declare function brandTokens(brand: ChapterBrand | undefined, options?: {
1244
1299
  selector?: string;
1245
1300
  }): string;
1301
+ /** Structural output accepted from `@odla-ai/brand`'s pure token compiler. */
1302
+ interface CompiledChapterBrandTokens {
1303
+ light: Record<string, string>;
1304
+ dark: Record<string, string>;
1305
+ }
1306
+ /**
1307
+ * Turn a complete compiled token pair into Chapter's brand contract without
1308
+ * coupling Chapter's runtime to the brand-book/agent package. Explicit values in
1309
+ * `base` win, so a host can make final per-site adjustments after compilation.
1310
+ */
1311
+ declare function chapterBrandFromTokens(compiled: CompiledChapterBrandTokens, base?: ChapterBrand): ChapterBrand;
1246
1312
 
1247
1313
  /** A `meetings` row, as far as reconciliation cares. */
1248
1314
  interface MeetingForReconcile {
@@ -1404,4 +1470,4 @@ type ApplicationBookingPatch = {
1404
1470
  * already there (never backward). */
1405
1471
  declare function applicationBookingUpdate(currentStatus: string, startAt: number, htmlLink?: string | null): ApplicationBookingPatch;
1406
1472
 
1407
- export { type AccountModel, type AdminNotificationTrigger, type Applicant, type ApplicationBookingPatch, type ApplicationRecord, type ApplicationSummary, type Attr, type AttrType, type Chapter, type ChapterApplication, type ChapterAuth, type ChapterBrand, type ChapterConfig, type ChapterDb, type ChapterEmails, type ChapterIntegrationDescriptor, type ChapterIntegrationOptions, type ChapterMode, type ChapterNetwork, type ChapterNetworkTarget, type ChapterOperations, type ChapterPipeline, type ChapterPolicy, type ChapterPrices, type ChapterScheduling, type ChapterSends, type ClerkInviteInput, type ClerkResult, type ClerkUserInput, type ClerkUserRecord, DEFAULT_SHARE_FIELDS, type DashboardMetricData, type DashboardMetricSeries, type DbOp, type DbRules, type DbSchema, type DeliveryDecision, type EmailGroup, type EmailTemplate, type EmailTemplateRow, type Entity, type ExistingMeeting, type GuardResult, type IntegrationDescriptor, type IntegrationProvision, type IntegrationSecret, type IntegrationSetting, type IntegrationSync, type JoinConfigGroup, type LiveEvent, type MailSender, type MeetingForReconcile, type MeetingRecord, type MeetingReschedulePatch, type MemberApplication, type MemberSession, type NewMeetingRow, type NotifyDeps, type NotifyInput, type NotifyResult, type PaymentsGroup, type ProjectionDeps, type ReconcileDecision, type ResolvedApplication, type ResolvedAuth, type ResolvedNetwork, type ResolvedNetworkTarget, type ResolvedOperations, type ResolvedPipeline, type ResolvedScheduling, type ResolvedSends, type RoleChangeContext, type Rule, SCHEDULING_DEFAULTS, type SchedulingErrors, type SecretMode, type SecretStore, type SessionUser, type SharedBusiness, type SharedPerson, type SharedRecord, type StripeEvent, type StripeResult, type SubmitResult, type WebhookEvent, applicantProfile, applicationBookingUpdate, applicationSummary, backfillCrm, bookingDecision, brandTokens, bucketSeries, buildGroupSeed, canApprove, canBook, canChangeRole, canTransition, canceledPatch, chapterDb, clampArray, clerkGetUser, clerkGetUserByEmail, clerkIntegration, clerkInviteRequest, clerkListUsers, clerkSetRole, clerkUserRequest, createChapterIntegration, createClerkInvitation, createClerkUser, dashboardMetricData, defaultCrm, defineChapter, emailGroupFrom, endForSlot, findApplicationRef, firstPaymentPatch, getVaultSecret, hasDisclaimerAck, introIdempotencyKey, isAdminRole, isAlreadySent, isReconcilable, isSlotAvailable, isValidEmail, joinConfig, meetingCreateRow, meetingRescheduleUpdate, memberApplication, memberSession, networkSourceTag, normalizeSharedRecord, normalizeWebhookEvent, paymentsReady, personInputFromApp, planDelivery, projectApplicant, projectSharedRecord, reconcileMeetings, refundedPatch, render, renderSummary, renderTemplateBody, renewalPatch, resolveApplication, resolveAuth, resolvePipeline, resolveScheduling, roleFromClaim, sendTemplated, sharedPersonInput, sharedRecordFromCrm, slotWindow, stageIndex, stripeCall, stripeForm, subAnnualCents, submitApplication, subscriptionIdempotencyKey, syncApplicationToCrm, validateScheduling, verifyStripeSignature, webhookMutationId };
1473
+ export { type AccountModel, type AdminNotificationTrigger, type Applicant, type ApplicationBookingPatch, type ApplicationRecord, type ApplicationSummary, type Attr, type AttrType, type Chapter, type ChapterApplication, type ChapterAuth, type ChapterBrand, type ChapterConfig, type ChapterCopy, type ChapterCopyInput, type ChapterDb, type ChapterEmails, type ChapterIntegrationDescriptor, type ChapterIntegrationOptions, type ChapterMode, type ChapterNetwork, type ChapterNetworkTarget, type ChapterOperations, type ChapterPipeline, type ChapterPolicy, type ChapterPrices, type ChapterScheduling, type ChapterSends, type ClerkInviteInput, type ClerkResult, type ClerkUserInput, type ClerkUserRecord, type CompiledChapterBrandTokens, DEFAULT_CHAPTER_COPY, DEFAULT_SHARE_FIELDS, type DashboardMetricData, type DashboardMetricSeries, type DbOp, type DbRules, type DbSchema, type DeliveryDecision, type EmailGroup, type EmailTemplate, type EmailTemplateRow, type Entity, type ExistingMeeting, type GuardResult, type IntegrationDescriptor, type IntegrationProvision, type IntegrationSecret, type IntegrationSetting, type IntegrationSync, type JoinConfigGroup, type LiveEvent, type MailSender, type MeetingForReconcile, type MeetingRecord, type MeetingReschedulePatch, type MemberApplication, type MemberSession, type NewMeetingRow, type NotifyDeps, type NotifyInput, type NotifyResult, type PaymentsGroup, type ProjectionDeps, type ReconcileDecision, type ResolvedApplication, type ResolvedAuth, type ResolvedNetwork, type ResolvedNetworkTarget, type ResolvedOperations, type ResolvedPipeline, type ResolvedScheduling, type ResolvedSends, type RoleChangeContext, type Rule, SCHEDULING_DEFAULTS, type SchedulingErrors, type SecretMode, type SecretStore, type SessionUser, type SharedBusiness, type SharedPerson, type SharedRecord, type StripeEvent, type StripeResult, type SubmitResult, type WebhookEvent, applicantProfile, applicationBookingUpdate, applicationSummary, backfillCrm, bookingDecision, brandTokens, bucketSeries, buildGroupSeed, canApprove, canBook, canChangeRole, canTransition, canceledPatch, chapterBrandFromTokens, chapterDb, clampArray, clerkGetUser, clerkGetUserByEmail, clerkIntegration, clerkInviteRequest, clerkListUsers, clerkSetRole, clerkUserRequest, createChapterIntegration, createClerkInvitation, createClerkUser, dashboardMetricData, defaultCrm, defineChapter, emailGroupFrom, endForSlot, findApplicationRef, firstPaymentPatch, formatChapterCopy, getVaultSecret, hasDisclaimerAck, introIdempotencyKey, isAdminRole, isAlreadySent, isReconcilable, isSlotAvailable, isValidEmail, joinConfig, meetingCreateRow, meetingRescheduleUpdate, memberApplication, memberSession, networkSourceTag, normalizeSharedRecord, normalizeWebhookEvent, paymentsReady, personInputFromApp, planDelivery, projectApplicant, projectSharedRecord, reconcileMeetings, refundedPatch, render, renderSummary, renderTemplateBody, renewalPatch, resolveApplication, resolveAuth, resolveChapterCopy, resolvePipeline, resolveScheduling, roleFromClaim, sendTemplated, sharedPersonInput, sharedRecordFromCrm, slotWindow, stageIndex, stripeCall, stripeForm, subAnnualCents, submitApplication, subscriptionIdempotencyKey, syncApplicationToCrm, validateScheduling, verifyStripeSignature, webhookMutationId };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,57 @@
1
1
  import { CrmConfig, Crm, CrmRecord } from '@odla-ai/crm';
2
2
 
3
+ /** Complete default language resolved beneath partial Chapter copy overrides. */
4
+ declare const DEFAULT_CHAPTER_COPY: ChapterCopy;
5
+ /** Resolve a partial copy contract and validate every supplied leaf. */
6
+ declare function resolveChapterCopy(input?: ChapterCopyInput): ChapterCopy;
7
+ /** Replace `{name}` placeholders without interpreting the resulting text as HTML. */
8
+ declare function formatChapterCopy(template: string, values: Readonly<Record<string, string | number>>): string;
9
+
10
+ type TextFields<Key extends string> = {
11
+ [Field in Key]: string;
12
+ };
13
+ /**
14
+ * Complete user-visible language owned by Chapter's packaged surfaces.
15
+ *
16
+ * Copy is serializable text; use render slots when presentation needs markup and
17
+ * `formatChapterCopy()` for typed placeholders.
18
+ */
19
+ interface ChapterCopy {
20
+ common: TextFields<"loading" | "saved" | "unknown">;
21
+ join: {
22
+ form: TextFields<"submit" | "submitting" | "submitFailed" | "unexpectedFailure">;
23
+ booking: TextFields<"unavailable" | "loadFailed" | "slotTaken" | "failed" | "loading" | "book" | "booking">;
24
+ payment: TextFields<"setupFailed" | "preparing" | "pending" | "processing" | "payAndContinue" | "incomplete">;
25
+ done: TextFields<"label" | "calendarInvite" | "memberArea">;
26
+ };
27
+ members: {
28
+ loadFailed: string;
29
+ account: TextFields<"signOut" | "adminConsole">;
30
+ provisional: TextFields<"cardLabel" | "applicationNeeded" | "applicationNeededBody" | "apply" | "refunded" | "refundedBody" | "active" | "renews" | "introductionCall" | "calendarInvite" | "joinCall" | "bookCall" | "bookCallBody" | "chooseTime">;
31
+ full: TextFields<"welcome">;
32
+ reschedule: TextFields<"changeTime" | "unavailable" | "loadFailed" | "slotGone" | "loading" | "noTimes" | "rescheduling" | "keepTime">;
33
+ };
34
+ admin: {
35
+ auth: TextFields<"checking" | "notAuthorized" | "accountNotAuthorized" | "thisAccount" | "signOut" | "loading" | "signInNotConfigured" | "missingPublishableKey" | "noWorkspaces" | "signInTagline">;
36
+ shell: TextFields<"adminRole" | "adminConsole" | "adminName" | "navigationLabel">;
37
+ workspaces: TextFields<"dashboard" | "overview" | "billing" | "people" | "settings" | "calendar" | "email" | "dashboardViewsLabel" | "settingsViewsLabel" | "collectionsLabel">;
38
+ dashboard: TextFields<"loading" | "loadFailed" | "upcomingCalls" | "callsNeedAttention" | "noUpcomingCalls" | "drift" | "open" | "meet" | "applications" | "newMembers" | "newMembersUnavailable" | "revenueAdded" | "revenue" | "revenueUnavailable" | "pipeline" | "pipelineLabel" | "thisWeek" | "noChange" | "activeMemberships" | "annualRunRate" | "testMode">;
39
+ billing: TextFields<"loading" | "loadFailed" | "notConfigured" | "active" | "annualized" | "renewingSoon" | "pastDue" | "subscriptions" | "testMode" | "truncated" | "name" | "email" | "application" | "subscription" | "cancelling" | "amount" | "renews">;
40
+ availability: TextFields<"loading" | "loadFailed" | "saved" | "title" | "days" | "startHour" | "endHour" | "slotMinutes" | "minNoticeHours" | "windowDays" | "timezone" | "summaryTemplate" | "save"> & {
41
+ dayLabels: readonly string[];
42
+ };
43
+ email: TextFields<"loading" | "loadFailed" | "saved" | "sent" | "testFailed" | "delivery" | "notificationAddress" | "replyTo" | "debugInbox" | "debugInboxHint" | "sendTest" | "enabled" | "subject" | "body" | "save" | "sendLog" | "sentColumn" | "templateColumn" | "statusColumn" | "toColumn" | "failed" | "redirected" | "delivered">;
44
+ meetings: TextFields<"loading" | "loadFailed" | "cancelConfirm" | "newStartPrompt" | "parseFailed" | "agenda" | "empty" | "unknown" | "drift" | "meet" | "reschedule" | "cancel">;
45
+ network: TextFields<"title" | "allowlistDescription" | "sharing" | "shareWith" | "shared" | "shareFailed" | "deliveryFailed">;
46
+ records: TextFields<"workflowMissing" | "comms" | "commsHistory" | "scheduling" | "billing" | "notes" | "access" | "sharing" | "lifecycle" | "role" | "superAdminHint" | "noAccount" | "approve" | "refund" | "approved" | "refunded" | "roleSet" | "messageSent" | "messageNotSent" | "loadingTemplates" | "template" | "chooseTemplate" | "sending" | "send" | "noMessages" | "message" | "newStartPrompt" | "parseFailed" | "cancelConfirm" | "callCancelled" | "callRescheduled" | "noApplication" | "loadingMeetings" | "noMeetings" | "joinMeeting" | "openCalendar" | "reschedule" | "cancel">;
47
+ };
48
+ }
49
+ type DeepPartial<T> = T extends readonly (infer Item)[] ? readonly Item[] : T extends object ? {
50
+ [Key in keyof T]?: DeepPartial<T[Key]>;
51
+ } : T;
52
+ /** Recursively partial copy overrides accepted by `defineChapter({ copy })`. */
53
+ type ChapterCopyInput = DeepPartial<ChapterCopy>;
54
+
3
55
  /** Which feature profile a site runs. `chapter` is the full public member site
4
56
  * (join, Stripe membership, booking, member area, admin, CRM); `hub` is
5
57
  * admin-only and CRM-focused (a directory/registry over the same CRM). */
@@ -93,6 +145,8 @@ interface ChapterBrandTokens {
93
145
  interface ChapterBrand {
94
146
  /** Preloaded @odla-ai/ui theme name used by ThemeScope. */
95
147
  theme?: string;
148
+ /** Named accent family supplied by the selected @odla-ai/ui theme. */
149
+ accent?: string;
96
150
  /** Admin/application color mode. Explicit light is the safe default. */
97
151
  colorScheme?: "light" | "dark" | "system";
98
152
  tokens?: ChapterBrandTokens;
@@ -105,6 +159,9 @@ interface ChapterBrand {
105
159
  * `:root[data-theme="dark"]` and `@media (prefers-color-scheme: dark)`, so a
106
160
  * site brands both modes. */
107
161
  paletteDark?: Record<string, string>;
162
+ /** Full token map for `.ui-invert` islands. A compiled custom theme normally
163
+ * uses its dark map here so inverted content never inherits stale composites. */
164
+ paletteInvert?: Record<string, string>;
108
165
  fonts?: {
109
166
  display?: string;
110
167
  body?: string;
@@ -260,17 +317,13 @@ interface ChapterApplication {
260
317
  /** Max JSON request body in bytes. Default 32768. */
261
318
  bodyCap?: number;
262
319
  /** Reject a submit that carries no truthy `disclaimerAck` (400), instead of
263
- * writing a row with no consent record. Default `false` for back-compat —
264
- * but turn it on if the disclaimer is a compliance record: a missing ack is
265
- * otherwise silent, permanent and unreconstructible. Failure is deterministic
266
- * and surfaces on the first test submit, not intermittently in production. */
320
+ * writing a row with no consent record. Default `true`. Set `false`
321
+ * deliberately only when the site renders no consent control. */
267
322
  requireDisclaimerAck?: boolean;
268
323
  /** Allowlist of fields that reach the Clerk account's client-readable
269
- * `public_metadata.profile`. Default (unset) projects every non-identity
270
- * configured field convenient, but it also exposes free-text and
271
- * third-party fields (`message`, `referral`). Set this to a curated list
272
- * (e.g. `["phone", "state", "focus"]`) to keep confidential fields db-only.
273
- * Expected to become required-in-spirit at 1.0. */
324
+ * `public_metadata.profile`. Default `[]`, so application details remain
325
+ * db-only. Set a curated list (e.g. `["phone", "state", "focus"]`) for
326
+ * fields the browser may read. */
274
327
  profileFields?: readonly string[];
275
328
  /** Extra application fields carried into the one-way CRM projection, on top of
276
329
  * the built-in identity/contact set. Each MUST be declared on your crm person
@@ -294,8 +347,8 @@ interface ResolvedApplication {
294
347
  defaultMaxLen: number;
295
348
  bodyCap: number;
296
349
  requireDisclaimerAck: boolean;
297
- /** Resolved Clerk-metadata allowlist; `null` means "all non-identity fields". */
298
- profileFields: readonly string[] | null;
350
+ /** Resolved Clerk-metadata allowlist. Empty means no profile projection. */
351
+ profileFields: readonly string[];
299
352
  crmFields: readonly string[];
300
353
  maxArrayLen: number;
301
354
  validateEmail: boolean;
@@ -311,6 +364,9 @@ interface ChapterConfig {
311
364
  /** A `defineCrm()` config or a resolved `Crm`. Omit for the per-mode default. */
312
365
  crm?: CrmConfig | Crm;
313
366
  brand?: ChapterBrand;
367
+ /** User-visible language for packaged Chapter surfaces. Recursively partial;
368
+ * `defineChapter()` resolves every omitted leaf from the package defaults. */
369
+ copy?: ChapterCopyInput;
314
370
  /** Leader → follower delivery targets. Secret values stay in the leader
315
371
  * tenant vault; see {@link ChapterNetworkTarget}. */
316
372
  network?: ChapterNetwork;
@@ -329,13 +385,10 @@ interface ChapterConfig {
329
385
  auth?: ChapterAuth;
330
386
  /** odla services (db implied). Default `["db","calendar","o11y"]`. */
331
387
  services?: readonly string[];
332
- /** Apply-time account provisioning. **Default `"none"`** it provisions
333
- * nothing, because the alternatives have an outbound side effect and a site
334
- * that never made the choice must not be mailing people. `"create"` makes the
335
- * Clerk account server-side (so join can say the account is ready);
336
- * `"invite"` **emails the applicant a Clerk invitation**. Both non-default
337
- * models need a `clerk_secret_key` vault secret to act. Opt in explicitly —
338
- * leaving this unset provisions no accounts. */
388
+ /** Apply-time account provisioning. Required in `chapter` mode so a site must
389
+ * make the decision explicitly. `"none"` provisions nothing, `"create"` makes
390
+ * the Clerk account server-side, and `"invite"` emails a real invitation.
391
+ * Both side-effecting models need a `clerk_secret_key` vault secret. */
339
392
  account?: AccountModel;
340
393
  /** WHEN lifecycle email fires. Addressing and content live on the group row
341
394
  * (owner-editable at runtime); this is the trigger, which is a build-time
@@ -403,6 +456,8 @@ interface Chapter {
403
456
  /** Resolved site identity. `wordmark` always falls back to `name`, so the
404
457
  * admin/member UI never needs a second brand declaration. */
405
458
  brand: ChapterBrand;
459
+ /** Fully resolved user-visible text for packaged Chapter surfaces. */
460
+ copy: ChapterCopy;
406
461
  /** Validated follower targets for leader-driven record pushes. */
407
462
  network: ResolvedNetwork;
408
463
  /** Resolved CRM engine (from `defineCrm`). */
@@ -417,7 +472,7 @@ interface Chapter {
417
472
  schema: DbSchema;
418
473
  rules: DbRules;
419
474
  services: readonly string[];
420
- /** Resolved apply-time account provisioning model (default `"none"`). */
475
+ /** Resolved apply-time account provisioning model. */
421
476
  account: AccountModel;
422
477
  /** Resolved send policy — when each lifecycle email fires. */
423
478
  sends: ResolvedSends;
@@ -803,10 +858,10 @@ declare function hasDisclaimerAck(fields: Record<string, unknown>): boolean;
803
858
  /**
804
859
  * The applicant profile written to the Clerk account's client-readable
805
860
  * `public_metadata.profile`. Projects each configured non-identity field, plus
806
- * `focus` (clamped) but ONLY those in `application.profileFields` when that
807
- * allowlist is set, so a site keeps confidential fields (`message`, `referral`)
808
- * db-only. Derived from config, so a site's own field names project without this
809
- * package knowing them. Pure; returns `undefined` when there is nothing to write.
861
+ * `focus` (clamped), but ONLY those in `application.profileFields`, so a site
862
+ * keeps confidential fields (`message`, `referral`) db-only. Derived from
863
+ * config, so a site's own field names project without this package knowing
864
+ * them. Pure; returns `undefined` when there is nothing to write.
810
865
  */
811
866
  declare function applicantProfile(chapter: Chapter, fields: Record<string, unknown>): Record<string, unknown> | undefined;
812
867
  /** A validated submission, or a 400-worthy validation error the route returns.
@@ -1243,6 +1298,17 @@ declare function memberSession(user: SessionUser, opts: {
1243
1298
  declare function brandTokens(brand: ChapterBrand | undefined, options?: {
1244
1299
  selector?: string;
1245
1300
  }): string;
1301
+ /** Structural output accepted from `@odla-ai/brand`'s pure token compiler. */
1302
+ interface CompiledChapterBrandTokens {
1303
+ light: Record<string, string>;
1304
+ dark: Record<string, string>;
1305
+ }
1306
+ /**
1307
+ * Turn a complete compiled token pair into Chapter's brand contract without
1308
+ * coupling Chapter's runtime to the brand-book/agent package. Explicit values in
1309
+ * `base` win, so a host can make final per-site adjustments after compilation.
1310
+ */
1311
+ declare function chapterBrandFromTokens(compiled: CompiledChapterBrandTokens, base?: ChapterBrand): ChapterBrand;
1246
1312
 
1247
1313
  /** A `meetings` row, as far as reconciliation cares. */
1248
1314
  interface MeetingForReconcile {
@@ -1404,4 +1470,4 @@ type ApplicationBookingPatch = {
1404
1470
  * already there (never backward). */
1405
1471
  declare function applicationBookingUpdate(currentStatus: string, startAt: number, htmlLink?: string | null): ApplicationBookingPatch;
1406
1472
 
1407
- export { type AccountModel, type AdminNotificationTrigger, type Applicant, type ApplicationBookingPatch, type ApplicationRecord, type ApplicationSummary, type Attr, type AttrType, type Chapter, type ChapterApplication, type ChapterAuth, type ChapterBrand, type ChapterConfig, type ChapterDb, type ChapterEmails, type ChapterIntegrationDescriptor, type ChapterIntegrationOptions, type ChapterMode, type ChapterNetwork, type ChapterNetworkTarget, type ChapterOperations, type ChapterPipeline, type ChapterPolicy, type ChapterPrices, type ChapterScheduling, type ChapterSends, type ClerkInviteInput, type ClerkResult, type ClerkUserInput, type ClerkUserRecord, DEFAULT_SHARE_FIELDS, type DashboardMetricData, type DashboardMetricSeries, type DbOp, type DbRules, type DbSchema, type DeliveryDecision, type EmailGroup, type EmailTemplate, type EmailTemplateRow, type Entity, type ExistingMeeting, type GuardResult, type IntegrationDescriptor, type IntegrationProvision, type IntegrationSecret, type IntegrationSetting, type IntegrationSync, type JoinConfigGroup, type LiveEvent, type MailSender, type MeetingForReconcile, type MeetingRecord, type MeetingReschedulePatch, type MemberApplication, type MemberSession, type NewMeetingRow, type NotifyDeps, type NotifyInput, type NotifyResult, type PaymentsGroup, type ProjectionDeps, type ReconcileDecision, type ResolvedApplication, type ResolvedAuth, type ResolvedNetwork, type ResolvedNetworkTarget, type ResolvedOperations, type ResolvedPipeline, type ResolvedScheduling, type ResolvedSends, type RoleChangeContext, type Rule, SCHEDULING_DEFAULTS, type SchedulingErrors, type SecretMode, type SecretStore, type SessionUser, type SharedBusiness, type SharedPerson, type SharedRecord, type StripeEvent, type StripeResult, type SubmitResult, type WebhookEvent, applicantProfile, applicationBookingUpdate, applicationSummary, backfillCrm, bookingDecision, brandTokens, bucketSeries, buildGroupSeed, canApprove, canBook, canChangeRole, canTransition, canceledPatch, chapterDb, clampArray, clerkGetUser, clerkGetUserByEmail, clerkIntegration, clerkInviteRequest, clerkListUsers, clerkSetRole, clerkUserRequest, createChapterIntegration, createClerkInvitation, createClerkUser, dashboardMetricData, defaultCrm, defineChapter, emailGroupFrom, endForSlot, findApplicationRef, firstPaymentPatch, getVaultSecret, hasDisclaimerAck, introIdempotencyKey, isAdminRole, isAlreadySent, isReconcilable, isSlotAvailable, isValidEmail, joinConfig, meetingCreateRow, meetingRescheduleUpdate, memberApplication, memberSession, networkSourceTag, normalizeSharedRecord, normalizeWebhookEvent, paymentsReady, personInputFromApp, planDelivery, projectApplicant, projectSharedRecord, reconcileMeetings, refundedPatch, render, renderSummary, renderTemplateBody, renewalPatch, resolveApplication, resolveAuth, resolvePipeline, resolveScheduling, roleFromClaim, sendTemplated, sharedPersonInput, sharedRecordFromCrm, slotWindow, stageIndex, stripeCall, stripeForm, subAnnualCents, submitApplication, subscriptionIdempotencyKey, syncApplicationToCrm, validateScheduling, verifyStripeSignature, webhookMutationId };
1473
+ export { type AccountModel, type AdminNotificationTrigger, type Applicant, type ApplicationBookingPatch, type ApplicationRecord, type ApplicationSummary, type Attr, type AttrType, type Chapter, type ChapterApplication, type ChapterAuth, type ChapterBrand, type ChapterConfig, type ChapterCopy, type ChapterCopyInput, type ChapterDb, type ChapterEmails, type ChapterIntegrationDescriptor, type ChapterIntegrationOptions, type ChapterMode, type ChapterNetwork, type ChapterNetworkTarget, type ChapterOperations, type ChapterPipeline, type ChapterPolicy, type ChapterPrices, type ChapterScheduling, type ChapterSends, type ClerkInviteInput, type ClerkResult, type ClerkUserInput, type ClerkUserRecord, type CompiledChapterBrandTokens, DEFAULT_CHAPTER_COPY, DEFAULT_SHARE_FIELDS, type DashboardMetricData, type DashboardMetricSeries, type DbOp, type DbRules, type DbSchema, type DeliveryDecision, type EmailGroup, type EmailTemplate, type EmailTemplateRow, type Entity, type ExistingMeeting, type GuardResult, type IntegrationDescriptor, type IntegrationProvision, type IntegrationSecret, type IntegrationSetting, type IntegrationSync, type JoinConfigGroup, type LiveEvent, type MailSender, type MeetingForReconcile, type MeetingRecord, type MeetingReschedulePatch, type MemberApplication, type MemberSession, type NewMeetingRow, type NotifyDeps, type NotifyInput, type NotifyResult, type PaymentsGroup, type ProjectionDeps, type ReconcileDecision, type ResolvedApplication, type ResolvedAuth, type ResolvedNetwork, type ResolvedNetworkTarget, type ResolvedOperations, type ResolvedPipeline, type ResolvedScheduling, type ResolvedSends, type RoleChangeContext, type Rule, SCHEDULING_DEFAULTS, type SchedulingErrors, type SecretMode, type SecretStore, type SessionUser, type SharedBusiness, type SharedPerson, type SharedRecord, type StripeEvent, type StripeResult, type SubmitResult, type WebhookEvent, applicantProfile, applicationBookingUpdate, applicationSummary, backfillCrm, bookingDecision, brandTokens, bucketSeries, buildGroupSeed, canApprove, canBook, canChangeRole, canTransition, canceledPatch, chapterBrandFromTokens, chapterDb, clampArray, clerkGetUser, clerkGetUserByEmail, clerkIntegration, clerkInviteRequest, clerkListUsers, clerkSetRole, clerkUserRequest, createChapterIntegration, createClerkInvitation, createClerkUser, dashboardMetricData, defaultCrm, defineChapter, emailGroupFrom, endForSlot, findApplicationRef, firstPaymentPatch, formatChapterCopy, getVaultSecret, hasDisclaimerAck, introIdempotencyKey, isAdminRole, isAlreadySent, isReconcilable, isSlotAvailable, isValidEmail, joinConfig, meetingCreateRow, meetingRescheduleUpdate, memberApplication, memberSession, networkSourceTag, normalizeSharedRecord, normalizeWebhookEvent, paymentsReady, personInputFromApp, planDelivery, projectApplicant, projectSharedRecord, reconcileMeetings, refundedPatch, render, renderSummary, renderTemplateBody, renewalPatch, resolveApplication, resolveAuth, resolveChapterCopy, resolvePipeline, resolveScheduling, roleFromClaim, sendTemplated, sharedPersonInput, sharedRecordFromCrm, slotWindow, stageIndex, stripeCall, stripeForm, subAnnualCents, submitApplication, subscriptionIdempotencyKey, syncApplicationToCrm, validateScheduling, verifyStripeSignature, webhookMutationId };