@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.js CHANGED
@@ -401,8 +401,8 @@ function resolveApplication(a) {
401
401
  maxLen: a?.maxLen ?? {},
402
402
  defaultMaxLen: a?.defaultMaxLen ?? 2e3,
403
403
  bodyCap: a?.bodyCap ?? 32768,
404
- requireDisclaimerAck: a?.requireDisclaimerAck ?? false,
405
- profileFields: a?.profileFields ?? null,
404
+ requireDisclaimerAck: a?.requireDisclaimerAck ?? true,
405
+ profileFields: a?.profileFields ?? [],
406
406
  crmFields: a?.crmFields ?? [],
407
407
  maxArrayLen: a?.maxArrayLen ?? 100,
408
408
  validateEmail: a?.validateEmail ?? true
@@ -422,7 +422,7 @@ function hasDisclaimerAck(fields) {
422
422
  var IDENTITY_FIELDS = /* @__PURE__ */ new Set(["email", "firstName", "lastName"]);
423
423
  function applicantProfile(chapter, fields) {
424
424
  const app = chapter.application;
425
- const allowed = (f) => app.profileFields === null || app.profileFields.includes(f);
425
+ const allowed = (f) => app.profileFields.includes(f);
426
426
  const profile = {};
427
427
  for (const f of [...app.required, ...app.optional]) {
428
428
  if (IDENTITY_FIELDS.has(f) || !allowed(f)) continue;
@@ -479,6 +479,299 @@ function joinConfig(group, paymentsReady2) {
479
479
  };
480
480
  }
481
481
 
482
+ // src/copy-defaults-admin.ts
483
+ var DEFAULT_ADMIN_COPY = {
484
+ auth: {
485
+ checking: "Checking access\u2026",
486
+ notAuthorized: "Not authorized",
487
+ accountNotAuthorized: "{account} isn't on the admin list.",
488
+ thisAccount: "This account",
489
+ signOut: "Sign out",
490
+ loading: "Loading\u2026",
491
+ signInNotConfigured: "Sign-in not configured",
492
+ missingPublishableKey: "No Clerk publishable key is set for this environment yet.",
493
+ noWorkspaces: "No admin workspaces are configured.",
494
+ signInTagline: "Admin sign-in, invite only"
495
+ },
496
+ shell: {
497
+ adminRole: "admin",
498
+ adminConsole: "Admin Console",
499
+ adminName: "Admin",
500
+ navigationLabel: "Admin navigation"
501
+ },
502
+ workspaces: {
503
+ dashboard: "Dashboard",
504
+ overview: "Overview",
505
+ billing: "Billing",
506
+ people: "People",
507
+ settings: "Settings",
508
+ calendar: "Calendar",
509
+ email: "Email",
510
+ dashboardViewsLabel: "Dashboard views",
511
+ settingsViewsLabel: "Settings views",
512
+ collectionsLabel: "CRM collections"
513
+ },
514
+ dashboard: {
515
+ loading: "Loading dashboard\u2026",
516
+ loadFailed: "Couldn't load the dashboard",
517
+ upcomingCalls: "Upcoming calls",
518
+ callsNeedAttention: "{count} need attention",
519
+ noUpcomingCalls: "No upcoming calls.",
520
+ drift: "drift",
521
+ open: "Open",
522
+ meet: "Meet",
523
+ applications: "Applications",
524
+ newMembers: "New members",
525
+ newMembersUnavailable: "Appears once billing is connected.",
526
+ revenueAdded: "Revenue \xB7 annual run rate added",
527
+ revenue: "Revenue",
528
+ revenueUnavailable: "Appears once billing is connected.",
529
+ pipeline: "Pipeline",
530
+ pipelineLabel: "Membership pipeline",
531
+ thisWeek: "+{count} this week",
532
+ noChange: "no change",
533
+ activeMemberships: "Active memberships",
534
+ annualRunRate: "Annual run rate",
535
+ testMode: "test mode"
536
+ },
537
+ billing: {
538
+ loading: "Loading\u2026",
539
+ loadFailed: "Couldn't load billing",
540
+ notConfigured: "Billing isn't configured. No Stripe key is vaulted for this group.",
541
+ active: "Active",
542
+ annualized: "Annualized",
543
+ renewingSoon: "Renewing within 60 days",
544
+ pastDue: "Past due",
545
+ subscriptions: "Subscriptions",
546
+ testMode: "Stripe test mode",
547
+ truncated: "Showing the first 100 subscriptions. The list is truncated.",
548
+ name: "Name",
549
+ email: "Email",
550
+ application: "Application",
551
+ subscription: "Subscription",
552
+ cancelling: "cancelling",
553
+ amount: "Amount",
554
+ renews: "Renews"
555
+ },
556
+ availability: {
557
+ loading: "Loading\u2026",
558
+ loadFailed: "Couldn't load availability",
559
+ saved: "Saved.",
560
+ title: "Booking availability",
561
+ days: "Days",
562
+ startHour: "Start hour (0\u201324)",
563
+ endHour: "End hour (0\u201324)",
564
+ slotMinutes: "Slot length (minutes)",
565
+ minNoticeHours: "Minimum notice (hours)",
566
+ windowDays: "Booking window (days)",
567
+ timezone: "Timezone",
568
+ summaryTemplate: "Event summary template",
569
+ save: "Save availability",
570
+ dayLabels: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
571
+ },
572
+ email: {
573
+ loading: "Loading\u2026",
574
+ loadFailed: "Couldn't load email config",
575
+ saved: "Saved.",
576
+ sent: "Sent {template} to {address}{redirected}.",
577
+ testFailed: "Test send did not deliver.",
578
+ delivery: "Delivery",
579
+ notificationAddress: "Notification address",
580
+ replyTo: "Reply-to",
581
+ debugInbox: "Debug inbox",
582
+ debugInboxHint: "Non-production sends redirect here.",
583
+ sendTest: "Send test",
584
+ enabled: "Enabled",
585
+ subject: "Subject",
586
+ body: "Body",
587
+ save: "Save changes",
588
+ sendLog: "Send log",
589
+ sentColumn: "Sent",
590
+ templateColumn: "Template",
591
+ statusColumn: "Status",
592
+ toColumn: "To",
593
+ failed: "failed",
594
+ redirected: "redirected",
595
+ delivered: "delivered"
596
+ },
597
+ meetings: {
598
+ loading: "Loading\u2026",
599
+ loadFailed: "Couldn't load meetings",
600
+ cancelConfirm: "Cancel this call? The calendar provider notifies the attendee.",
601
+ newStartPrompt: "New start time (for example, 2026-08-01 14:00):",
602
+ parseFailed: "Couldn't parse that time.",
603
+ agenda: "Agenda",
604
+ empty: "No meetings.",
605
+ unknown: "(unknown)",
606
+ drift: "drift",
607
+ meet: "Meet",
608
+ reschedule: "Reschedule",
609
+ cancel: "Cancel"
610
+ },
611
+ network: {
612
+ title: "Share to a follower",
613
+ allowlistDescription: "Sends the target's allowlisted fields. Its pipeline and account state remain local.",
614
+ sharing: "Sharing\u2026",
615
+ shareWith: "Share with {target}",
616
+ shared: "Shared with {target}.",
617
+ shareFailed: "Could not share with {target}.",
618
+ deliveryFailed: "Delivery failed."
619
+ },
620
+ records: {
621
+ workflowMissing: "This record is not linked to an application workflow.",
622
+ comms: "Comms",
623
+ commsHistory: "Comms history",
624
+ scheduling: "Scheduling",
625
+ billing: "Billing",
626
+ notes: "Notes",
627
+ access: "Access",
628
+ sharing: "Sharing",
629
+ lifecycle: "Access & lifecycle",
630
+ role: "Role",
631
+ superAdminHint: "Super-admin, managed in odla Studio.",
632
+ noAccount: "No linked account yet. The role is set once they sign in.",
633
+ approve: "Approve",
634
+ refund: "Refund",
635
+ approved: "Approved.",
636
+ refunded: "Refund issued.",
637
+ roleSet: "Role set to {role}.",
638
+ messageSent: "Message sent.",
639
+ messageNotSent: "Message was not sent.",
640
+ loadingTemplates: "Loading templates\u2026",
641
+ template: "Template",
642
+ chooseTemplate: "Choose a template\u2026",
643
+ sending: "Sending\u2026",
644
+ send: "Send",
645
+ noMessages: "No messages recorded.",
646
+ message: "Message",
647
+ newStartPrompt: "New start time:",
648
+ parseFailed: "Couldn't parse that time.",
649
+ cancelConfirm: "Cancel this call?",
650
+ callCancelled: "Call cancelled.",
651
+ callRescheduled: "Call rescheduled.",
652
+ noApplication: "No application is linked to this record.",
653
+ loadingMeetings: "Loading meetings\u2026",
654
+ noMeetings: "No meetings recorded.",
655
+ joinMeeting: "Join meeting",
656
+ openCalendar: "Open calendar",
657
+ reschedule: "Reschedule",
658
+ cancel: "Cancel"
659
+ }
660
+ };
661
+
662
+ // src/copy-defaults.ts
663
+ var DEFAULT_COMMON_COPY = {
664
+ loading: "Loading\u2026",
665
+ saved: "Saved.",
666
+ unknown: "(unknown)"
667
+ };
668
+ var DEFAULT_JOIN_COPY = {
669
+ form: {
670
+ submit: "Submit application",
671
+ submitting: "Submitting\u2026",
672
+ submitFailed: "Your application could not be submitted.",
673
+ unexpectedFailure: "Something went wrong. Please try again."
674
+ },
675
+ booking: {
676
+ unavailable: "Scheduling is briefly unavailable. We'll reach out by email to arrange your call.",
677
+ loadFailed: "Times are briefly unavailable. Please try again.",
678
+ slotTaken: "That time was just taken. Here are the current openings.",
679
+ failed: "The booking could not be completed.",
680
+ loading: "Loading available times\u2026",
681
+ book: "Book this time",
682
+ booking: "Booking\u2026"
683
+ },
684
+ payment: {
685
+ setupFailed: "Payment could not be set up. Please try again.",
686
+ preparing: "Preparing secure payment\u2026",
687
+ pending: "Confirming your payment\u2026",
688
+ processing: "Processing\u2026",
689
+ payAndContinue: "Pay and continue",
690
+ incomplete: "The payment did not complete. Please try again."
691
+ },
692
+ done: {
693
+ label: "You're booked",
694
+ calendarInvite: "A calendar invitation with the video call link is on its way to your email.",
695
+ memberArea: "Go to your member area"
696
+ }
697
+ };
698
+ var DEFAULT_MEMBERS_COPY = {
699
+ loadFailed: "Sign in is briefly unavailable. Please refresh.",
700
+ account: {
701
+ signOut: "Sign out",
702
+ adminConsole: "Admin console"
703
+ },
704
+ provisional: {
705
+ cardLabel: "Your Application",
706
+ applicationNeeded: "One step remains",
707
+ applicationNeededBody: "Your account is ready, and the application that completes it takes a few minutes.",
708
+ apply: "Apply for membership",
709
+ refunded: "Membership refunded",
710
+ refundedBody: "Your fee has been refunded in full and your membership is canceled.",
711
+ active: "Your membership is active.",
712
+ renews: "Your membership is active and renews {date}.",
713
+ introductionCall: "Your introduction call",
714
+ calendarInvite: "A calendar invitation with the video call link is in your email.",
715
+ joinCall: "Join the video call",
716
+ bookCall: "Book your introduction call",
717
+ bookCallBody: "Your application is in. Choose a time below, and a calendar invitation will reach your email.",
718
+ chooseTime: "Choose a time"
719
+ },
720
+ full: {
721
+ welcome: "Welcome back."
722
+ },
723
+ reschedule: {
724
+ changeTime: "Change your time",
725
+ unavailable: "Scheduling is briefly unavailable. We'll reach out by email to arrange your call.",
726
+ loadFailed: "Times are briefly unavailable. Please try again.",
727
+ slotGone: "That time is no longer available. Please pick another.",
728
+ loading: "Loading available times\u2026",
729
+ noTimes: "No open times right now. Please check back soon.",
730
+ rescheduling: "Rescheduling\u2026",
731
+ keepTime: "Keep my current time"
732
+ }
733
+ };
734
+
735
+ // src/copy-resolve.ts
736
+ var DEFAULT_CHAPTER_COPY = {
737
+ common: DEFAULT_COMMON_COPY,
738
+ join: DEFAULT_JOIN_COPY,
739
+ members: DEFAULT_MEMBERS_COPY,
740
+ admin: DEFAULT_ADMIN_COPY
741
+ };
742
+ var isObject = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
743
+ function mergeCopy(base, input, path) {
744
+ if (typeof base === "string") {
745
+ if (input === void 0) return base;
746
+ if (typeof input !== "string") throw new Error(`${path}: must be a string`);
747
+ return input;
748
+ }
749
+ if (Array.isArray(base)) {
750
+ if (input === void 0) return [...base];
751
+ if (!Array.isArray(input) || !input.every((item) => typeof item === "string")) {
752
+ throw new Error(`${path}: must be an array of strings`);
753
+ }
754
+ return [...input];
755
+ }
756
+ if (!isObject(base)) return base;
757
+ if (input !== void 0 && !isObject(input)) throw new Error(`${path}: must be an object`);
758
+ const overrides = input;
759
+ const result = {};
760
+ for (const [key, value] of Object.entries(base)) {
761
+ result[key] = mergeCopy(value, overrides?.[key], `${path}.${key}`);
762
+ }
763
+ return result;
764
+ }
765
+ function resolveChapterCopy(input) {
766
+ return mergeCopy(DEFAULT_CHAPTER_COPY, input, "defineChapter.copy");
767
+ }
768
+ function formatChapterCopy(template, values) {
769
+ return template.replace(
770
+ /\{([a-zA-Z][a-zA-Z0-9]*)\}/g,
771
+ (match, key) => Object.prototype.hasOwnProperty.call(values, key) ? String(values[key]) : match
772
+ );
773
+ }
774
+
482
775
  // src/config.ts
483
776
  var SLUG = /^[a-z0-9][a-z0-9-]{1,62}$/;
484
777
  var FIELD = /^[a-z][a-zA-Z0-9_]*$/;
@@ -557,11 +850,15 @@ function defineChapter(config) {
557
850
  if (!config.prices || typeof config.prices.standardCents !== "number") {
558
851
  throw new Error("defineChapter.prices.standardCents: required in chapter mode");
559
852
  }
853
+ if (config.account === void 0) {
854
+ throw new Error('defineChapter.account: required in chapter mode; choose "none", "create", or "invite" explicitly');
855
+ }
560
856
  }
561
857
  const auth = resolveAuth(mode, config.auth);
562
858
  const pipeline = resolvePipeline(config.pipeline);
563
859
  const application = resolveApplication(config.application);
564
860
  const brand = { ...config.brand, wordmark: config.brand?.wordmark ?? name };
861
+ const copy = resolveChapterCopy(config.copy);
565
862
  const network = resolveNetwork(config, crm);
566
863
  const { schema, rules } = chapterDb(mode, auth);
567
864
  const services = config.services ?? ["db", "calendar", "o11y"];
@@ -616,6 +913,7 @@ function defineChapter(config) {
616
913
  name,
617
914
  mode,
618
915
  brand,
916
+ copy,
619
917
  network,
620
918
  crm,
621
919
  auth,
@@ -1437,6 +1735,7 @@ function brandTokens(brand, options = {}) {
1437
1735
  if (fonts?.body) light.push(`--ui-font-sans: ${cleanValue(fonts.body)};`);
1438
1736
  if (fonts?.numeral) light.push(`--ui-font-numeral: ${cleanValue(fonts.numeral)};`);
1439
1737
  const dark = [...paletteDecls(brand.paletteDark), ...semanticDecls(brand.tokensDark)];
1738
+ const invert = paletteDecls(brand.paletteInvert);
1440
1739
  const selector = options.selector ?? ":root";
1441
1740
  const darkSelector = selector === ":root" ? ':root[data-theme="dark"]' : `${selector}[data-theme="dark"]`;
1442
1741
  const systemSelector = selector === ":root" ? ':root:not([data-theme="light"])' : `${selector}:not([data-theme="light"])`;
@@ -1452,10 +1751,26 @@ function brandTokens(brand, options = {}) {
1452
1751
  @media (prefers-color-scheme: dark) {
1453
1752
  ${systemSelector} ${block}
1454
1753
  }
1754
+ `;
1755
+ }
1756
+ if (invert.length) {
1757
+ const invertSelector = selector === ":root" ? ".ui-invert" : `${selector}.ui-invert, ${selector} .ui-invert`;
1758
+ css += `${invertSelector} {
1759
+ color-scheme: dark;
1760
+ ${invert.join("\n ")}
1761
+ }
1455
1762
  `;
1456
1763
  }
1457
1764
  return css;
1458
1765
  }
1766
+ function chapterBrandFromTokens(compiled, base = {}) {
1767
+ return {
1768
+ ...base,
1769
+ palette: { ...compiled.light, ...base.palette },
1770
+ paletteDark: { ...compiled.dark, ...base.paletteDark },
1771
+ paletteInvert: { ...compiled.dark, ...base.paletteInvert }
1772
+ };
1773
+ }
1459
1774
 
1460
1775
  // src/reconcile.ts
1461
1776
  function isReconcilable(meeting, now) {
@@ -1596,6 +1911,7 @@ function applicationBookingUpdate(currentStatus, startAt, htmlLink) {
1596
1911
  };
1597
1912
  }
1598
1913
  export {
1914
+ DEFAULT_CHAPTER_COPY,
1599
1915
  DEFAULT_SHARE_FIELDS,
1600
1916
  SCHEDULING_DEFAULTS,
1601
1917
  applicantProfile,
@@ -1611,6 +1927,7 @@ export {
1611
1927
  canChangeRole,
1612
1928
  canTransition,
1613
1929
  canceledPatch,
1930
+ chapterBrandFromTokens,
1614
1931
  chapterDb,
1615
1932
  clampArray,
1616
1933
  clerkGetUser,
@@ -1630,6 +1947,7 @@ export {
1630
1947
  endForSlot,
1631
1948
  findApplicationRef,
1632
1949
  firstPaymentPatch,
1950
+ formatChapterCopy,
1633
1951
  getVaultSecret,
1634
1952
  hasDisclaimerAck,
1635
1953
  introIdempotencyKey,
@@ -1659,6 +1977,7 @@ export {
1659
1977
  renewalPatch,
1660
1978
  resolveApplication,
1661
1979
  resolveAuth,
1980
+ resolveChapterCopy,
1662
1981
  resolvePipeline,
1663
1982
  resolveScheduling,
1664
1983
  roleFromClaim,