@odla-ai/chapter 0.22.1 → 0.24.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,64 @@
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
+ /**
25
+ * `consent` is the affirmative statement a member agrees to before the card
26
+ * form appears, rendered beside the checkbox and beneath the policy text.
27
+ * It is what the member is shown at the moment of consent, so it says they
28
+ * agree rather than merely restating the policy. Set it to an empty string
29
+ * to render the checkbox with the policy as its only label.
30
+ */
31
+ payment: TextFields<"setupFailed" | "preparing" | "pending" | "processing" | "payAndContinue" | "incomplete" | "consent">;
32
+ done: TextFields<"label" | "calendarInvite" | "memberArea">;
33
+ };
34
+ members: {
35
+ loadFailed: string;
36
+ account: TextFields<"signOut" | "adminConsole">;
37
+ provisional: TextFields<"cardLabel" | "applicationNeeded" | "applicationNeededBody" | "apply" | "refunded" | "refundedBody" | "active" | "renews" | "introductionCall" | "calendarInvite" | "joinCall" | "bookCall" | "bookCallBody" | "chooseTime">;
38
+ full: TextFields<"welcome">;
39
+ reschedule: TextFields<"changeTime" | "unavailable" | "loadFailed" | "slotGone" | "loading" | "noTimes" | "rescheduling" | "keepTime">;
40
+ };
41
+ admin: {
42
+ auth: TextFields<"checking" | "notAuthorized" | "accountNotAuthorized" | "thisAccount" | "signOut" | "loading" | "signInNotConfigured" | "missingPublishableKey" | "noWorkspaces" | "signInTagline">;
43
+ shell: TextFields<"adminRole" | "adminConsole" | "adminName" | "navigationLabel">;
44
+ workspaces: TextFields<"dashboard" | "overview" | "billing" | "people" | "settings" | "calendar" | "email" | "dashboardViewsLabel" | "settingsViewsLabel" | "collectionsLabel">;
45
+ dashboard: TextFields<"loading" | "loadFailed" | "upcomingCalls" | "callsNeedAttention" | "noUpcomingCalls" | "drift" | "open" | "meet" | "applications" | "newMembers" | "newMembersUnavailable" | "revenueAdded" | "revenue" | "revenueUnavailable" | "pipeline" | "pipelineLabel" | "thisWeek" | "noChange" | "activeMemberships" | "annualRunRate" | "testMode">;
46
+ billing: TextFields<"loading" | "loadFailed" | "notConfigured" | "active" | "annualized" | "renewingSoon" | "pastDue" | "subscriptions" | "testMode" | "truncated" | "name" | "email" | "application" | "subscription" | "cancelling" | "amount" | "renews">;
47
+ availability: TextFields<"loading" | "loadFailed" | "saved" | "title" | "days" | "startHour" | "endHour" | "slotMinutes" | "minNoticeHours" | "windowDays" | "timezone" | "summaryTemplate" | "save"> & {
48
+ dayLabels: readonly string[];
49
+ };
50
+ 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">;
51
+ meetings: TextFields<"loading" | "loadFailed" | "cancelConfirm" | "newStartPrompt" | "parseFailed" | "agenda" | "empty" | "unknown" | "drift" | "meet" | "reschedule" | "cancel">;
52
+ network: TextFields<"title" | "allowlistDescription" | "sharing" | "shareWith" | "shared" | "shareFailed" | "deliveryFailed">;
53
+ 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">;
54
+ };
55
+ }
56
+ type DeepPartial<T> = T extends readonly (infer Item)[] ? readonly Item[] : T extends object ? {
57
+ [Key in keyof T]?: DeepPartial<T[Key]>;
58
+ } : T;
59
+ /** Recursively partial copy overrides accepted by `defineChapter({ copy })`. */
60
+ type ChapterCopyInput = DeepPartial<ChapterCopy>;
61
+
3
62
  /** Which feature profile a site runs. `chapter` is the full public member site
4
63
  * (join, Stripe membership, booking, member area, admin, CRM); `hub` is
5
64
  * admin-only and CRM-focused (a directory/registry over the same CRM). */
@@ -93,6 +152,8 @@ interface ChapterBrandTokens {
93
152
  interface ChapterBrand {
94
153
  /** Preloaded @odla-ai/ui theme name used by ThemeScope. */
95
154
  theme?: string;
155
+ /** Named accent family supplied by the selected @odla-ai/ui theme. */
156
+ accent?: string;
96
157
  /** Admin/application color mode. Explicit light is the safe default. */
97
158
  colorScheme?: "light" | "dark" | "system";
98
159
  tokens?: ChapterBrandTokens;
@@ -105,6 +166,9 @@ interface ChapterBrand {
105
166
  * `:root[data-theme="dark"]` and `@media (prefers-color-scheme: dark)`, so a
106
167
  * site brands both modes. */
107
168
  paletteDark?: Record<string, string>;
169
+ /** Full token map for `.ui-invert` islands. A compiled custom theme normally
170
+ * uses its dark map here so inverted content never inherits stale composites. */
171
+ paletteInvert?: Record<string, string>;
108
172
  fonts?: {
109
173
  display?: string;
110
174
  body?: string;
@@ -260,17 +324,13 @@ interface ChapterApplication {
260
324
  /** Max JSON request body in bytes. Default 32768. */
261
325
  bodyCap?: number;
262
326
  /** 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. */
327
+ * writing a row with no consent record. Default `true`. Set `false`
328
+ * deliberately only when the site renders no consent control. */
267
329
  requireDisclaimerAck?: boolean;
268
330
  /** 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. */
331
+ * `public_metadata.profile`. Default `[]`, so application details remain
332
+ * db-only. Set a curated list (e.g. `["phone", "state", "focus"]`) for
333
+ * fields the browser may read. */
274
334
  profileFields?: readonly string[];
275
335
  /** Extra application fields carried into the one-way CRM projection, on top of
276
336
  * the built-in identity/contact set. Each MUST be declared on your crm person
@@ -294,8 +354,8 @@ interface ResolvedApplication {
294
354
  defaultMaxLen: number;
295
355
  bodyCap: number;
296
356
  requireDisclaimerAck: boolean;
297
- /** Resolved Clerk-metadata allowlist; `null` means "all non-identity fields". */
298
- profileFields: readonly string[] | null;
357
+ /** Resolved Clerk-metadata allowlist. Empty means no profile projection. */
358
+ profileFields: readonly string[];
299
359
  crmFields: readonly string[];
300
360
  maxArrayLen: number;
301
361
  validateEmail: boolean;
@@ -311,6 +371,9 @@ interface ChapterConfig {
311
371
  /** A `defineCrm()` config or a resolved `Crm`. Omit for the per-mode default. */
312
372
  crm?: CrmConfig | Crm;
313
373
  brand?: ChapterBrand;
374
+ /** User-visible language for packaged Chapter surfaces. Recursively partial;
375
+ * `defineChapter()` resolves every omitted leaf from the package defaults. */
376
+ copy?: ChapterCopyInput;
314
377
  /** Leader → follower delivery targets. Secret values stay in the leader
315
378
  * tenant vault; see {@link ChapterNetworkTarget}. */
316
379
  network?: ChapterNetwork;
@@ -329,13 +392,10 @@ interface ChapterConfig {
329
392
  auth?: ChapterAuth;
330
393
  /** odla services (db implied). Default `["db","calendar","o11y"]`. */
331
394
  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. */
395
+ /** Apply-time account provisioning. Required in `chapter` mode so a site must
396
+ * make the decision explicitly. `"none"` provisions nothing, `"create"` makes
397
+ * the Clerk account server-side, and `"invite"` emails a real invitation.
398
+ * Both side-effecting models need a `clerk_secret_key` vault secret. */
339
399
  account?: AccountModel;
340
400
  /** WHEN lifecycle email fires. Addressing and content live on the group row
341
401
  * (owner-editable at runtime); this is the trigger, which is a build-time
@@ -403,6 +463,8 @@ interface Chapter {
403
463
  /** Resolved site identity. `wordmark` always falls back to `name`, so the
404
464
  * admin/member UI never needs a second brand declaration. */
405
465
  brand: ChapterBrand;
466
+ /** Fully resolved user-visible text for packaged Chapter surfaces. */
467
+ copy: ChapterCopy;
406
468
  /** Validated follower targets for leader-driven record pushes. */
407
469
  network: ResolvedNetwork;
408
470
  /** Resolved CRM engine (from `defineCrm`). */
@@ -417,7 +479,7 @@ interface Chapter {
417
479
  schema: DbSchema;
418
480
  rules: DbRules;
419
481
  services: readonly string[];
420
- /** Resolved apply-time account provisioning model (default `"none"`). */
482
+ /** Resolved apply-time account provisioning model. */
421
483
  account: AccountModel;
422
484
  /** Resolved send policy — when each lifecycle email fires. */
423
485
  sends: ResolvedSends;
@@ -466,6 +528,20 @@ interface ChapterIntegrationDescriptor {
466
528
  path: string;
467
529
  expectedStatus: number;
468
530
  }>;
531
+ /** Runbooks that bear on THIS chapter's role, so tooling can point at the
532
+ * right procedure without the operator knowing it exists. A chapter declaring
533
+ * `network.targets` is a leader hub; one declaring none is a follower site,
534
+ * and the two follow different procedures for the same words ("connect",
535
+ * "share records"). */
536
+ runbooks: ChapterRunbookHints;
537
+ }
538
+ /** Which runbooks apply, and why. */
539
+ interface ChapterRunbookHints {
540
+ role: "leader" | "follower";
541
+ /** Platform runbook slugs, most relevant first. */
542
+ slugs: string[];
543
+ /** One line a human or agent can act on. */
544
+ guidance: string;
469
545
  }
470
546
  /**
471
547
  * Build the one CLI-consumable integration for a chapter/hub: the crm_*
@@ -803,10 +879,10 @@ declare function hasDisclaimerAck(fields: Record<string, unknown>): boolean;
803
879
  /**
804
880
  * The applicant profile written to the Clerk account's client-readable
805
881
  * `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.
882
+ * `focus` (clamped), but ONLY those in `application.profileFields`, so a site
883
+ * keeps confidential fields (`message`, `referral`) db-only. Derived from
884
+ * config, so a site's own field names project without this package knowing
885
+ * them. Pure; returns `undefined` when there is nothing to write.
810
886
  */
811
887
  declare function applicantProfile(chapter: Chapter, fields: Record<string, unknown>): Record<string, unknown> | undefined;
812
888
  /** A validated submission, or a 400-worthy validation error the route returns.
@@ -1243,6 +1319,17 @@ declare function memberSession(user: SessionUser, opts: {
1243
1319
  declare function brandTokens(brand: ChapterBrand | undefined, options?: {
1244
1320
  selector?: string;
1245
1321
  }): string;
1322
+ /** Structural output accepted from `@odla-ai/brand`'s pure token compiler. */
1323
+ interface CompiledChapterBrandTokens {
1324
+ light: Record<string, string>;
1325
+ dark: Record<string, string>;
1326
+ }
1327
+ /**
1328
+ * Turn a complete compiled token pair into Chapter's brand contract without
1329
+ * coupling Chapter's runtime to the brand-book/agent package. Explicit values in
1330
+ * `base` win, so a host can make final per-site adjustments after compilation.
1331
+ */
1332
+ declare function chapterBrandFromTokens(compiled: CompiledChapterBrandTokens, base?: ChapterBrand): ChapterBrand;
1246
1333
 
1247
1334
  /** A `meetings` row, as far as reconciliation cares. */
1248
1335
  interface MeetingForReconcile {
@@ -1404,4 +1491,4 @@ type ApplicationBookingPatch = {
1404
1491
  * already there (never backward). */
1405
1492
  declare function applicationBookingUpdate(currentStatus: string, startAt: number, htmlLink?: string | null): ApplicationBookingPatch;
1406
1493
 
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 };
1494
+ 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 ChapterRunbookHints, 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,64 @@
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
+ /**
25
+ * `consent` is the affirmative statement a member agrees to before the card
26
+ * form appears, rendered beside the checkbox and beneath the policy text.
27
+ * It is what the member is shown at the moment of consent, so it says they
28
+ * agree rather than merely restating the policy. Set it to an empty string
29
+ * to render the checkbox with the policy as its only label.
30
+ */
31
+ payment: TextFields<"setupFailed" | "preparing" | "pending" | "processing" | "payAndContinue" | "incomplete" | "consent">;
32
+ done: TextFields<"label" | "calendarInvite" | "memberArea">;
33
+ };
34
+ members: {
35
+ loadFailed: string;
36
+ account: TextFields<"signOut" | "adminConsole">;
37
+ provisional: TextFields<"cardLabel" | "applicationNeeded" | "applicationNeededBody" | "apply" | "refunded" | "refundedBody" | "active" | "renews" | "introductionCall" | "calendarInvite" | "joinCall" | "bookCall" | "bookCallBody" | "chooseTime">;
38
+ full: TextFields<"welcome">;
39
+ reschedule: TextFields<"changeTime" | "unavailable" | "loadFailed" | "slotGone" | "loading" | "noTimes" | "rescheduling" | "keepTime">;
40
+ };
41
+ admin: {
42
+ auth: TextFields<"checking" | "notAuthorized" | "accountNotAuthorized" | "thisAccount" | "signOut" | "loading" | "signInNotConfigured" | "missingPublishableKey" | "noWorkspaces" | "signInTagline">;
43
+ shell: TextFields<"adminRole" | "adminConsole" | "adminName" | "navigationLabel">;
44
+ workspaces: TextFields<"dashboard" | "overview" | "billing" | "people" | "settings" | "calendar" | "email" | "dashboardViewsLabel" | "settingsViewsLabel" | "collectionsLabel">;
45
+ dashboard: TextFields<"loading" | "loadFailed" | "upcomingCalls" | "callsNeedAttention" | "noUpcomingCalls" | "drift" | "open" | "meet" | "applications" | "newMembers" | "newMembersUnavailable" | "revenueAdded" | "revenue" | "revenueUnavailable" | "pipeline" | "pipelineLabel" | "thisWeek" | "noChange" | "activeMemberships" | "annualRunRate" | "testMode">;
46
+ billing: TextFields<"loading" | "loadFailed" | "notConfigured" | "active" | "annualized" | "renewingSoon" | "pastDue" | "subscriptions" | "testMode" | "truncated" | "name" | "email" | "application" | "subscription" | "cancelling" | "amount" | "renews">;
47
+ availability: TextFields<"loading" | "loadFailed" | "saved" | "title" | "days" | "startHour" | "endHour" | "slotMinutes" | "minNoticeHours" | "windowDays" | "timezone" | "summaryTemplate" | "save"> & {
48
+ dayLabels: readonly string[];
49
+ };
50
+ 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">;
51
+ meetings: TextFields<"loading" | "loadFailed" | "cancelConfirm" | "newStartPrompt" | "parseFailed" | "agenda" | "empty" | "unknown" | "drift" | "meet" | "reschedule" | "cancel">;
52
+ network: TextFields<"title" | "allowlistDescription" | "sharing" | "shareWith" | "shared" | "shareFailed" | "deliveryFailed">;
53
+ 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">;
54
+ };
55
+ }
56
+ type DeepPartial<T> = T extends readonly (infer Item)[] ? readonly Item[] : T extends object ? {
57
+ [Key in keyof T]?: DeepPartial<T[Key]>;
58
+ } : T;
59
+ /** Recursively partial copy overrides accepted by `defineChapter({ copy })`. */
60
+ type ChapterCopyInput = DeepPartial<ChapterCopy>;
61
+
3
62
  /** Which feature profile a site runs. `chapter` is the full public member site
4
63
  * (join, Stripe membership, booking, member area, admin, CRM); `hub` is
5
64
  * admin-only and CRM-focused (a directory/registry over the same CRM). */
@@ -93,6 +152,8 @@ interface ChapterBrandTokens {
93
152
  interface ChapterBrand {
94
153
  /** Preloaded @odla-ai/ui theme name used by ThemeScope. */
95
154
  theme?: string;
155
+ /** Named accent family supplied by the selected @odla-ai/ui theme. */
156
+ accent?: string;
96
157
  /** Admin/application color mode. Explicit light is the safe default. */
97
158
  colorScheme?: "light" | "dark" | "system";
98
159
  tokens?: ChapterBrandTokens;
@@ -105,6 +166,9 @@ interface ChapterBrand {
105
166
  * `:root[data-theme="dark"]` and `@media (prefers-color-scheme: dark)`, so a
106
167
  * site brands both modes. */
107
168
  paletteDark?: Record<string, string>;
169
+ /** Full token map for `.ui-invert` islands. A compiled custom theme normally
170
+ * uses its dark map here so inverted content never inherits stale composites. */
171
+ paletteInvert?: Record<string, string>;
108
172
  fonts?: {
109
173
  display?: string;
110
174
  body?: string;
@@ -260,17 +324,13 @@ interface ChapterApplication {
260
324
  /** Max JSON request body in bytes. Default 32768. */
261
325
  bodyCap?: number;
262
326
  /** 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. */
327
+ * writing a row with no consent record. Default `true`. Set `false`
328
+ * deliberately only when the site renders no consent control. */
267
329
  requireDisclaimerAck?: boolean;
268
330
  /** 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. */
331
+ * `public_metadata.profile`. Default `[]`, so application details remain
332
+ * db-only. Set a curated list (e.g. `["phone", "state", "focus"]`) for
333
+ * fields the browser may read. */
274
334
  profileFields?: readonly string[];
275
335
  /** Extra application fields carried into the one-way CRM projection, on top of
276
336
  * the built-in identity/contact set. Each MUST be declared on your crm person
@@ -294,8 +354,8 @@ interface ResolvedApplication {
294
354
  defaultMaxLen: number;
295
355
  bodyCap: number;
296
356
  requireDisclaimerAck: boolean;
297
- /** Resolved Clerk-metadata allowlist; `null` means "all non-identity fields". */
298
- profileFields: readonly string[] | null;
357
+ /** Resolved Clerk-metadata allowlist. Empty means no profile projection. */
358
+ profileFields: readonly string[];
299
359
  crmFields: readonly string[];
300
360
  maxArrayLen: number;
301
361
  validateEmail: boolean;
@@ -311,6 +371,9 @@ interface ChapterConfig {
311
371
  /** A `defineCrm()` config or a resolved `Crm`. Omit for the per-mode default. */
312
372
  crm?: CrmConfig | Crm;
313
373
  brand?: ChapterBrand;
374
+ /** User-visible language for packaged Chapter surfaces. Recursively partial;
375
+ * `defineChapter()` resolves every omitted leaf from the package defaults. */
376
+ copy?: ChapterCopyInput;
314
377
  /** Leader → follower delivery targets. Secret values stay in the leader
315
378
  * tenant vault; see {@link ChapterNetworkTarget}. */
316
379
  network?: ChapterNetwork;
@@ -329,13 +392,10 @@ interface ChapterConfig {
329
392
  auth?: ChapterAuth;
330
393
  /** odla services (db implied). Default `["db","calendar","o11y"]`. */
331
394
  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. */
395
+ /** Apply-time account provisioning. Required in `chapter` mode so a site must
396
+ * make the decision explicitly. `"none"` provisions nothing, `"create"` makes
397
+ * the Clerk account server-side, and `"invite"` emails a real invitation.
398
+ * Both side-effecting models need a `clerk_secret_key` vault secret. */
339
399
  account?: AccountModel;
340
400
  /** WHEN lifecycle email fires. Addressing and content live on the group row
341
401
  * (owner-editable at runtime); this is the trigger, which is a build-time
@@ -403,6 +463,8 @@ interface Chapter {
403
463
  /** Resolved site identity. `wordmark` always falls back to `name`, so the
404
464
  * admin/member UI never needs a second brand declaration. */
405
465
  brand: ChapterBrand;
466
+ /** Fully resolved user-visible text for packaged Chapter surfaces. */
467
+ copy: ChapterCopy;
406
468
  /** Validated follower targets for leader-driven record pushes. */
407
469
  network: ResolvedNetwork;
408
470
  /** Resolved CRM engine (from `defineCrm`). */
@@ -417,7 +479,7 @@ interface Chapter {
417
479
  schema: DbSchema;
418
480
  rules: DbRules;
419
481
  services: readonly string[];
420
- /** Resolved apply-time account provisioning model (default `"none"`). */
482
+ /** Resolved apply-time account provisioning model. */
421
483
  account: AccountModel;
422
484
  /** Resolved send policy — when each lifecycle email fires. */
423
485
  sends: ResolvedSends;
@@ -466,6 +528,20 @@ interface ChapterIntegrationDescriptor {
466
528
  path: string;
467
529
  expectedStatus: number;
468
530
  }>;
531
+ /** Runbooks that bear on THIS chapter's role, so tooling can point at the
532
+ * right procedure without the operator knowing it exists. A chapter declaring
533
+ * `network.targets` is a leader hub; one declaring none is a follower site,
534
+ * and the two follow different procedures for the same words ("connect",
535
+ * "share records"). */
536
+ runbooks: ChapterRunbookHints;
537
+ }
538
+ /** Which runbooks apply, and why. */
539
+ interface ChapterRunbookHints {
540
+ role: "leader" | "follower";
541
+ /** Platform runbook slugs, most relevant first. */
542
+ slugs: string[];
543
+ /** One line a human or agent can act on. */
544
+ guidance: string;
469
545
  }
470
546
  /**
471
547
  * Build the one CLI-consumable integration for a chapter/hub: the crm_*
@@ -803,10 +879,10 @@ declare function hasDisclaimerAck(fields: Record<string, unknown>): boolean;
803
879
  /**
804
880
  * The applicant profile written to the Clerk account's client-readable
805
881
  * `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.
882
+ * `focus` (clamped), but ONLY those in `application.profileFields`, so a site
883
+ * keeps confidential fields (`message`, `referral`) db-only. Derived from
884
+ * config, so a site's own field names project without this package knowing
885
+ * them. Pure; returns `undefined` when there is nothing to write.
810
886
  */
811
887
  declare function applicantProfile(chapter: Chapter, fields: Record<string, unknown>): Record<string, unknown> | undefined;
812
888
  /** A validated submission, or a 400-worthy validation error the route returns.
@@ -1243,6 +1319,17 @@ declare function memberSession(user: SessionUser, opts: {
1243
1319
  declare function brandTokens(brand: ChapterBrand | undefined, options?: {
1244
1320
  selector?: string;
1245
1321
  }): string;
1322
+ /** Structural output accepted from `@odla-ai/brand`'s pure token compiler. */
1323
+ interface CompiledChapterBrandTokens {
1324
+ light: Record<string, string>;
1325
+ dark: Record<string, string>;
1326
+ }
1327
+ /**
1328
+ * Turn a complete compiled token pair into Chapter's brand contract without
1329
+ * coupling Chapter's runtime to the brand-book/agent package. Explicit values in
1330
+ * `base` win, so a host can make final per-site adjustments after compilation.
1331
+ */
1332
+ declare function chapterBrandFromTokens(compiled: CompiledChapterBrandTokens, base?: ChapterBrand): ChapterBrand;
1246
1333
 
1247
1334
  /** A `meetings` row, as far as reconciliation cares. */
1248
1335
  interface MeetingForReconcile {
@@ -1404,4 +1491,4 @@ type ApplicationBookingPatch = {
1404
1491
  * already there (never backward). */
1405
1492
  declare function applicationBookingUpdate(currentStatus: string, startAt: number, htmlLink?: string | null): ApplicationBookingPatch;
1406
1493
 
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 };
1494
+ 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 ChapterRunbookHints, 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 };