@odla-ai/chapter 0.51.0 → 0.52.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +102 -8
- package/dist/{chunk-4EA5V4JE.js → chunk-7N4VDWM5.js} +239 -136
- package/dist/chunk-7N4VDWM5.js.map +1 -0
- package/dist/{chunk-UAXWA5BL.js → chunk-7RIT2UN5.js} +2 -2
- package/dist/{chunk-JXX4BFDP.js → chunk-A6W53GVM.js} +5 -1
- package/dist/chunk-A6W53GVM.js.map +1 -0
- package/dist/{copy-context-Ddat__H5.d.ts → copy-context-CYLlPzAc.d.ts} +11 -5
- package/dist/index.cjs +88 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +70 -12
- package/dist/index.d.ts +70 -12
- package/dist/index.js +88 -7
- package/dist/index.js.map +1 -1
- package/dist/ui/admin/index.d.ts +14 -3
- package/dist/ui/admin/index.js +4 -2
- package/dist/ui/index.d.ts +2 -2
- package/dist/ui/index.js +5 -3
- package/dist/ui/member/index.d.ts +2 -2
- package/dist/ui/member/index.js +2 -2
- package/dist/worker/index.cjs +912 -354
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +47 -9
- package/dist/worker/index.d.ts +47 -9
- package/dist/worker/index.js +912 -354
- package/dist/worker/index.js.map +1 -1
- package/package.json +2 -1
- package/dist/chunk-4EA5V4JE.js.map +0 -1
- package/dist/chunk-JXX4BFDP.js.map +0 -1
- /package/dist/{chunk-UAXWA5BL.js.map → chunk-7RIT2UN5.js.map} +0 -0
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
DEFAULT_JOIN_COPY,
|
|
3
3
|
formatChapterCopy,
|
|
4
4
|
useChapterCopy
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-A6W53GVM.js";
|
|
6
6
|
import {
|
|
7
7
|
collectFormFields
|
|
8
8
|
} from "./chunk-IVXVECKR.js";
|
|
@@ -2035,4 +2035,4 @@ export {
|
|
|
2035
2035
|
membershipApplicationHref,
|
|
2036
2036
|
MembershipCards
|
|
2037
2037
|
};
|
|
2038
|
-
//# sourceMappingURL=chunk-
|
|
2038
|
+
//# sourceMappingURL=chunk-7RIT2UN5.js.map
|
|
@@ -108,6 +108,8 @@ var DEFAULT_ADMIN_COPY = {
|
|
|
108
108
|
delivery: "Delivery",
|
|
109
109
|
notificationAddress: "Notification address",
|
|
110
110
|
replyTo: "Reply-to",
|
|
111
|
+
fromName: "From name",
|
|
112
|
+
fromNameHint: "Shown as the sender; the address stays the Worker's configured one.",
|
|
111
113
|
debugInbox: "Debug inbox",
|
|
112
114
|
debugInboxHint: "Non-production sends redirect here.",
|
|
113
115
|
sendTest: "Send test",
|
|
@@ -179,6 +181,8 @@ var DEFAULT_ADMIN_COPY = {
|
|
|
179
181
|
transportReachable: "transport reachable",
|
|
180
182
|
transportUnavailable: "transport unavailable",
|
|
181
183
|
transportNotChecked: "transport not checked",
|
|
184
|
+
edgeUnpaired: "edge unpaired",
|
|
185
|
+
edgeUnpairedHint: "This follower's shared secret is not in the leader vault, so nothing can be signed. Pair both vaults from an enrolled machine:",
|
|
182
186
|
bindingTransport: "service binding {binding}",
|
|
183
187
|
urlTransport: "public URL transport",
|
|
184
188
|
activeConfiguration: "active configuration r{revision}",
|
|
@@ -516,4 +520,4 @@ export {
|
|
|
516
520
|
useChapterCopy,
|
|
517
521
|
BrandStyle
|
|
518
522
|
};
|
|
519
|
-
//# sourceMappingURL=chunk-
|
|
523
|
+
//# sourceMappingURL=chunk-A6W53GVM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ui/copy-context.tsx","../src/copy-defaults-admin.ts","../src/copy-defaults.ts","../src/copy-resolve.ts","../src/brand.ts","../src/ui/brand-style.tsx"],"sourcesContent":["import { createContext, type ComponentChildren } from \"preact\";\nimport { useContext } from \"preact/hooks\";\nimport {\n DEFAULT_CHAPTER_COPY,\n resolveChapterCopy,\n type ChapterCopy,\n type ChapterCopyInput,\n} from \"../copy.js\";\n\nconst ChapterCopyContext = createContext<ChapterCopy>(DEFAULT_CHAPTER_COPY);\n\n/** Place a resolved or partial copy contract around any Chapter UI surface. */\nexport function ChapterCopyProvider(props: {\n copy?: ChapterCopy | ChapterCopyInput;\n children: ComponentChildren;\n}) {\n return (\n <ChapterCopyContext.Provider value={resolveChapterCopy(props.copy)}>\n {props.children}\n </ChapterCopyContext.Provider>\n );\n}\n\n/** Read the nearest complete Chapter copy contract. */\nexport function useChapterCopy(): ChapterCopy {\n return useContext(ChapterCopyContext);\n}\n","import type { ChapterCopy } from \"./copy.js\";\n\nexport const DEFAULT_ADMIN_COPY: ChapterCopy[\"admin\"] = {\n auth: {\n checking: \"Checking access…\",\n notAuthorized: \"Not authorized\",\n accountNotAuthorized: \"{account} isn't on the admin list.\",\n thisAccount: \"This account\",\n signOut: \"Sign out\",\n loading: \"Loading…\",\n signInNotConfigured: \"Sign-in not configured\",\n missingPublishableKey: \"No Clerk publishable key is set for this environment yet.\",\n noWorkspaces: \"No admin workspaces are configured.\",\n signInTagline: \"Admin sign-in, invite only\",\n },\n shell: {\n adminRole: \"admin\",\n adminConsole: \"Admin Console\",\n adminName: \"Admin\",\n navigationLabel: \"Admin navigation\",\n },\n workspaces: {\n dashboard: \"Dashboard\",\n overview: \"Overview\",\n billing: \"Billing\",\n people: \"People\",\n admission: \"Admission\",\n network: \"Network\",\n portfolio: \"Portfolio\",\n settings: \"Settings\",\n calendar: \"Calendar\",\n email: \"Email\",\n chapters: \"Chapters\",\n formations: \"Formation applications\",\n dealFlow: \"Deal flow\",\n dashboardViewsLabel: \"Dashboard views\",\n settingsViewsLabel: \"Settings views\",\n collectionsLabel: \"CRM collections\",\n portfolioViewsLabel: \"Portfolio views\",\n },\n dashboard: {\n loading: \"Loading dashboard…\",\n loadFailed: \"Couldn't load the dashboard\",\n upcomingCalls: \"Upcoming calls\",\n callsNeedAttention: \"{count} need attention\",\n noUpcomingCalls: \"No upcoming calls.\",\n drift: \"drift\",\n open: \"Open\",\n meet: \"Meet\",\n applications: \"Applications\",\n newMembers: \"New members\",\n newMembersUnavailable: \"Appears once billing is connected.\",\n revenueAdded: \"Revenue · annual run rate added\",\n revenue: \"Revenue\",\n revenueUnavailable: \"Appears once billing is connected.\",\n pipeline: \"Pipeline\",\n pipelineLabel: \"Membership pipeline\",\n thisWeek: \"+{count} this week\",\n noChange: \"no change\",\n activeMemberships: \"Active memberships\",\n annualRunRate: \"Annual run rate\",\n testMode: \"test mode\",\n },\n billing: {\n loading: \"Loading…\",\n loadFailed: \"Couldn't load billing\",\n notConfigured: \"Billing isn't configured. No Stripe key is vaulted for this group.\",\n active: \"Active\",\n annualized: \"Annualized\",\n renewingSoon: \"Renewing within 60 days\",\n pastDue: \"Past due\",\n subscriptions: \"Subscriptions\",\n testMode: \"Stripe test mode\",\n truncated: \"Showing the first 100 subscriptions. The list is truncated.\",\n name: \"Name\",\n email: \"Email\",\n application: \"Application\",\n subscription: \"Subscription\",\n cancelling: \"cancelling\",\n amount: \"Amount\",\n renews: \"Renews\",\n },\n availability: {\n loading: \"Loading…\",\n loadFailed: \"Couldn't load availability\",\n saved: \"Saved.\",\n title: \"Booking availability\",\n days: \"Days\",\n startHour: \"Start hour (0–24)\",\n endHour: \"End hour (0–24)\",\n slotMinutes: \"Slot length (minutes)\",\n minNoticeHours: \"Minimum notice (hours)\",\n windowDays: \"Booking window (days)\",\n timezone: \"Timezone\",\n summaryTemplate: \"Event summary template\",\n save: \"Save availability\",\n dayLabels: [\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"],\n },\n email: {\n loading: \"Loading…\",\n loadFailed: \"Couldn't load email config\",\n saved: \"Saved.\",\n sent: \"Sent {template} to {address}{redirected}.\",\n testFailed: \"Test send did not deliver.\",\n delivery: \"Delivery\",\n notificationAddress: \"Notification address\",\n replyTo: \"Reply-to\",\n fromName: \"From name\",\n fromNameHint: \"Shown as the sender; the address stays the Worker's configured one.\",\n debugInbox: \"Debug inbox\",\n debugInboxHint: \"Non-production sends redirect here.\",\n sendTest: \"Send test\",\n enabled: \"Enabled\",\n subject: \"Subject\",\n body: \"Body\",\n save: \"Save changes\",\n sendLog: \"Send log\",\n sentColumn: \"Sent\",\n templateColumn: \"Template\",\n statusColumn: \"Status\",\n toColumn: \"To\",\n failed: \"failed\",\n redirected: \"redirected\",\n delivered: \"delivered\",\n },\n meetings: {\n loading: \"Loading…\",\n loadFailed: \"Couldn't load meetings\",\n cancelConfirm: \"Cancel this call? The calendar provider notifies the attendee.\",\n newStartPrompt: \"New start time (for example, 2026-08-01 14:00):\",\n parseFailed: \"Couldn't parse that time.\",\n agenda: \"Agenda\",\n empty: \"No meetings.\",\n unknown: \"(unknown)\",\n drift: \"drift\",\n meet: \"Meet\",\n reschedule: \"Reschedule\",\n cancel: \"Cancel\",\n },\n admission: {\n title: \"Interview decisions\",\n description: \"Local decisions override Parent decisions. Requiring an interview locally suppresses a Parent waiver for the same email.\",\n email: \"Email\",\n decision: \"Decision\",\n expires: \"Expires\",\n note: \"Internal note\",\n waive: \"Waive interview\",\n require: \"Require interview\",\n add: \"Add decision\",\n ledger: \"Effective grant ledger\",\n loading: \"Loading…\",\n consumed: \"consumed\",\n revoked: \"revoked\",\n expired: \"expired\",\n active: \"active\",\n local: \"local\",\n parent: \"Parent\",\n revoke: \"Revoke\",\n },\n network: {\n title: \"Share to a follower\",\n allowlistDescription: \"Sends the target's allowlisted fields. Its pipeline and account state remain local.\",\n sharing: \"Sharing…\",\n shareWith: \"Share with {target}\",\n shared: \"Shared with {target}.\",\n shareFailed: \"Could not share with {target}.\",\n deliveryFailed: \"Delivery failed.\",\n loadingRollup: \"Loading network overview…\",\n loadRollupFailed: \"Couldn't load the network overview\",\n overview: \"Network overview\",\n configuredFollowers: \"Configured followers\",\n reachableFollowers: \"Reachable followers\",\n recordsAcrossFollowers: \"Records across followers\",\n noFollowers: \"No follower sites are configured.\",\n followers: \"Followers\",\n available: \"available\",\n unavailable: \"unavailable\",\n transportReachable: \"transport reachable\",\n transportUnavailable: \"transport unavailable\",\n transportNotChecked: \"transport not checked\",\n edgeUnpaired: \"edge unpaired\",\n edgeUnpairedHint: \"This follower's shared secret is not in the leader vault, so nothing can be signed. Pair both vaults from an enrolled machine:\",\n bindingTransport: \"service binding {binding}\",\n urlTransport: \"public URL transport\",\n activeConfiguration: \"active configuration r{revision}\",\n noActiveConfiguration: \"no active managed configuration\",\n publicSiteAvailable: \"public site reachable\",\n publicSiteUnavailable: \"public site unavailable\",\n publicSiteNotChecked: \"public site not probed\",\n recordTotals: \"Portfolio records\",\n noRecordTotals: \"No aggregate records are available yet.\",\n },\n records: {\n workflowMissing: \"This record is not linked to an application workflow.\",\n invitation: \"Invitation\",\n comms: \"Comms\",\n commsHistory: \"Comms history\",\n scheduling: \"Scheduling\",\n billing: \"Billing\",\n notes: \"Notes\",\n access: \"Access\",\n sharing: \"Sharing\",\n lifecycle: \"Access & lifecycle\",\n role: \"Role\",\n superAdminHint: \"Super-admin, managed in odla Studio.\",\n noAccount: \"No linked account yet. The role is set once they sign in.\",\n approve: \"Approve\",\n refund: \"Refund\",\n approved: \"Approved.\",\n refunded: \"Refund issued.\",\n roleSet: \"Role set to {role}.\",\n messageSent: \"Message sent.\",\n messageNotSent: \"Message was not sent.\",\n loadingTemplates: \"Loading templates…\",\n template: \"Template\",\n chooseTemplate: \"Choose a template…\",\n sending: \"Sending…\",\n send: \"Send\",\n noMessages: \"No messages recorded.\",\n message: \"Message\",\n newStartPrompt: \"New start time:\",\n parseFailed: \"Couldn't parse that time.\",\n cancelConfirm: \"Cancel this call?\",\n callCancelled: \"Call cancelled.\",\n callRescheduled: \"Call rescheduled.\",\n noApplication: \"No application is linked to this record.\",\n loadingMeetings: \"Loading meetings…\",\n noMeetings: \"No meetings recorded.\",\n joinMeeting: \"Join meeting\",\n openCalendar: \"Open calendar\",\n reschedule: \"Reschedule\",\n cancel: \"Cancel\",\n },\n};\n","import type { ChapterCopy } from \"./copy.js\";\n\nexport const DEFAULT_COMMON_COPY: ChapterCopy[\"common\"] = {\n loading: \"Loading…\",\n saved: \"Saved.\",\n unknown: \"(unknown)\",\n};\n\nexport const DEFAULT_JOIN_COPY: ChapterCopy[\"join\"] = {\n namedSeat: {\n title: \"Your membership invitation\",\n from: \"{purchaserName} invited you.\",\n prepaid: \"Your {tierName} seat is already paid for.\",\n body: \"Accept your invitation, then complete your own application requirements. You will not be asked to pay for this seat.\",\n consent: \"I accept this membership for myself. It cannot be transferred and remains current while the purchaser's membership and my seat are current.\",\n accept: \"Accept my invitation\", accepting: \"Confirming your invitation…\", loading: \"Loading your invitation…\",\n unavailable: \"Your invitation is unavailable. Sign in with the invited email address, then try again.\",\n retry: \"Try again\", failure: \"We could not confirm acceptance. Retry this invitation; you do not need to purchase another membership.\",\n },\n form: {\n submit: \"Submit application\",\n submitting: \"Submitting…\",\n submitFailed: \"Your application could not be submitted.\",\n unexpectedFailure: \"Something went wrong. Please try again.\",\n },\n booking: {\n title: \"Book your introduction\",\n intro: \"Choose a time to meet your chapter and get to know each other.\",\n unavailable: \"Scheduling is briefly unavailable. We'll reach out by email to arrange your call.\",\n loadFailed: \"Times are briefly unavailable. Please try again.\",\n slotTaken: \"That time was just taken. Here are the current openings.\",\n failed: \"The booking could not be completed.\",\n loading: \"Loading available times…\",\n book: \"Book this time\",\n booking: \"Booking…\",\n },\n payment: {\n setupFailed: \"Payment could not be set up. Please try again.\",\n preparing: \"Preparing secure payment…\",\n pending: \"Confirming your payment…\",\n processing: \"Processing…\",\n payAndContinue: \"Pay and continue\",\n incomplete: \"The payment did not complete. Please try again.\",\n consent: \"I have read and agree to the pricing and refund policy above.\",\n foundingMember: \"Founding member rate\",\n foundingDiscount: \"Founding discount\",\n },\n done: {\n label: \"You're booked\",\n calendarInvite: \"A calendar invitation with the video call link is on its way to your email.\",\n memberArea: \"Go to your member area\",\n },\n accepted: {\n label: \"You're approved\",\n body: \"Your membership is approved. No introduction call is required.\",\n },\n gift: {\n chooseSelf: \"Buy membership for myself\",\n optionsLabel: \"Or give access to someone else\",\n label: \"Your gift code\",\n body: \"Share this one-time code with the recipient. It becomes active after payment is confirmed.\",\n save: \"Save it now. For security, this code is shown only once.\",\n rotate: \"Generate a new code\",\n rotateFailed: \"A new code could not be generated.\",\n claimTitle: \"Have a gift code?\",\n claimBody: \"Use it here instead of paying by card.\",\n code: \"One-time code\",\n redeem: \"Use gift code\",\n redeemed: \"Gift code applied.\",\n redeemFailed: \"The code is invalid, unavailable, or does not match this membership.\",\n },\n};\n\nexport const DEFAULT_MEMBERS_COPY: ChapterCopy[\"members\"] = {\n loadFailed: \"Sign in is briefly unavailable. Please refresh.\",\n account: {\n signOut: \"Sign out\",\n adminConsole: \"Admin console\",\n },\n provisional: {\n cardLabel: \"Your Application\",\n applicationNeeded: \"One step remains\",\n applicationNeededBody: \"Your account is ready, and the application that completes it takes a few minutes.\",\n apply: \"Apply for membership\",\n refunded: \"Membership refunded\",\n refundedBody: \"Your fee has been refunded in full and your membership is canceled.\",\n active: \"Your membership is active.\",\n renews: \"Your membership is active and renews {date}.\",\n introductionCall: \"Your introduction call\",\n calendarInvite: \"A calendar invitation with the video call link is in your email.\",\n joinCall: \"Join the video call\",\n bookCall: \"Book your introduction call\",\n bookCallBody: \"Your application is in. Choose a time below, and a calendar invitation will reach your email.\",\n chooseTime: \"Choose a time\",\n },\n full: {\n welcome: \"Welcome back.\",\n },\n gifts: {\n title: \"Give access\",\n body: \"Purchase access for someone else and share the one-time claim code.\",\n noneAvailable: \"No gift options are available right now.\",\n paid: \"Payment submitted. Save the code below while it is visible.\",\n saveCode: \"This code is shown once. Save it before leaving this page.\",\n purchases: \"Your gifts\",\n noPurchases: \"No gift purchases yet.\",\n pending: \"Pending\",\n redeemed: \"Redeemed\",\n expired: \"Expired\",\n revoked: \"Revoked\",\n rotate: \"Rotate code\",\n rotated: \"A new code was generated. Save it now.\",\n rotateFailed: \"The code could not be rotated.\",\n redeemTitle: \"Redeem a gift\",\n code: \"One-time code\",\n redeem: \"Redeem\",\n redeemFailed: \"The code is invalid or unavailable.\",\n },\n reschedule: {\n changeTime: \"Change your time\",\n unavailable: \"Scheduling is briefly unavailable. We'll reach out by email to arrange your call.\",\n loadFailed: \"Times are briefly unavailable. Please try again.\",\n slotGone: \"That time is no longer available. Please pick another.\",\n loading: \"Loading available times…\",\n noTimes: \"No open times right now. Please check back soon.\",\n rescheduling: \"Rescheduling…\",\n keepTime: \"Keep my current time\",\n },\n};\n","import { DEFAULT_ADMIN_COPY } from \"./copy-defaults-admin.js\";\nimport {\n DEFAULT_COMMON_COPY,\n DEFAULT_JOIN_COPY,\n DEFAULT_MEMBERS_COPY,\n} from \"./copy-defaults.js\";\nimport type { ChapterCopy, ChapterCopyInput } from \"./copy.js\";\n\n/** Complete default language resolved beneath partial Chapter copy overrides. */\nexport const DEFAULT_CHAPTER_COPY: ChapterCopy = {\n common: DEFAULT_COMMON_COPY,\n join: DEFAULT_JOIN_COPY,\n members: DEFAULT_MEMBERS_COPY,\n admin: DEFAULT_ADMIN_COPY,\n};\n\nconst isObject = (value: unknown): value is Record<string, unknown> =>\n Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n\nfunction mergeCopy<T>(base: T, input: unknown, path: string): T {\n if (typeof base === \"string\") {\n if (input === undefined) return base;\n if (typeof input !== \"string\") throw new Error(`${path}: must be a string`);\n return input as T;\n }\n if (Array.isArray(base)) {\n if (input === undefined) return [...base] as T;\n if (!Array.isArray(input) || !input.every((item) => typeof item === \"string\")) {\n throw new Error(`${path}: must be an array of strings`);\n }\n return [...input] as T;\n }\n if (!isObject(base)) return base;\n if (input !== undefined && !isObject(input)) throw new Error(`${path}: must be an object`);\n const overrides = input as Record<string, unknown> | undefined;\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(base)) {\n result[key] = mergeCopy(value, overrides?.[key], `${path}.${key}`);\n }\n return result as T;\n}\n\n/** Resolve a partial copy contract and validate every supplied leaf. */\nexport function resolveChapterCopy(input?: ChapterCopyInput): ChapterCopy {\n return mergeCopy(DEFAULT_CHAPTER_COPY, input, \"defineChapter.copy\");\n}\n\n/** Replace `{name}` placeholders without interpreting the resulting text as HTML. */\nexport function formatChapterCopy(\n template: string,\n values: Readonly<Record<string, string | number>>,\n): string {\n return template.replace(/\\{([a-zA-Z][a-zA-Z0-9]*)\\}/g, (match, key: string) =>\n Object.prototype.hasOwnProperty.call(values, key) ? String(values[key]) : match,\n );\n}\n","// Brand tokens (H4). defineChapter accepts a `brand` block; this turns it into a\n// `:root { --…: … }` CSS block, so a chapter re-skins the WHOLE UI — the\n// @odla-ai/ui components, the admin shell, and the member islands, which all read\n// --ui-* design tokens — from one config instead of hand-writing inline CSS.\n//\n// Pure string generation, so it is unit-testable and can be emitted at\n// build/SSR time into the page <head> (no flash of unstyled content), or via the\n// <BrandStyle> component from @odla-ai/chapter/ui.\nimport type { ChapterBrand, ChapterBrandTokens } from \"./types\";\n\n// A palette entry is either a direct custom property (already `--…`, e.g.\n// `--ui-accent` to retheme components) or a bare name we expose as `--<name>`\n// (e.g. `moss` → `--moss`, for a site to reference in its own CSS).\nfunction paletteVar(key: string): string {\n return key.startsWith(\"--\") ? key : `--${key}`;\n}\n\n// Strip characters that could break out of a `--var: value;` declaration or the\n// surrounding <style>. Brand config is trusted author input, so this is a\n// belt-and-suspenders guard, not a security boundary.\nfunction cleanValue(value: string): string {\n return value.replace(/[<>{};]/g, \"\").trim();\n}\n\nfunction paletteDecls(palette: Record<string, string> | undefined): string[] {\n const decls: string[] = [];\n for (const [key, value] of Object.entries(palette ?? {})) {\n if (typeof value === \"string\" && value.trim()) decls.push(`${paletteVar(key)}: ${cleanValue(value)};`);\n }\n return decls;\n}\n\nconst TOKEN_VARS: Record<keyof ChapterBrandTokens, string> = {\n background: \"--ui-bg\",\n surface: \"--ui-surface\",\n surface2: \"--ui-surface-2\",\n text: \"--ui-text\",\n textMuted: \"--ui-text-muted\",\n textFaint: \"--ui-text-faint\",\n border: \"--ui-border\",\n borderStrong: \"--ui-border-strong\",\n accent: \"--ui-accent\",\n accentStrong: \"--ui-accent-strong\",\n accentSoft: \"--ui-accent-soft\",\n onAccent: \"--ui-on-accent\",\n good: \"--ui-good\",\n warn: \"--ui-warn\",\n danger: \"--ui-danger\",\n chart1: \"--ui-chart-1\",\n chart2: \"--ui-chart-2\",\n chart3: \"--ui-chart-3\",\n chart4: \"--ui-chart-4\",\n chart5: \"--ui-chart-5\",\n chart6: \"--ui-chart-6\",\n chartPositive: \"--ui-chart-pos\",\n chartNegative: \"--ui-chart-neg\",\n contentWidth: \"--ui-content-width\",\n panelRadius: \"--ui-radius-lg\",\n panelShadow: \"--ui-shadow\",\n masterDetailColumns: \"--ui-master-detail-columns\",\n masterDetailMinHeight: \"--ui-master-detail-min-height\",\n pagePadding: \"--chapter-admin-page-padding\",\n workspacePadding: \"--chapter-admin-workspace-padding\",\n};\n\nfunction semanticDecls(tokens: ChapterBrandTokens | undefined): string[] {\n return Object.entries(tokens ?? {}).flatMap(([key, value]) =>\n typeof value === \"string\" && value.trim()\n ? [`${TOKEN_VARS[key as keyof ChapterBrandTokens]}: ${cleanValue(value)};`]\n : [],\n );\n}\n\n/**\n * Build the CSS that maps a chapter's brand onto the design tokens the UI reads:\n * each `palette` entry becomes a custom property (light, and dark unless\n * `paletteDark` overrides), and `fonts` (display/body/numeral) map to\n * `--ui-font-display` / `--ui-font-sans` / `--ui-font-numeral`. The dark block is\n * emitted under both `:root[data-theme=\"dark\"]` (the odla-ui theme toggle) and\n * `@media (prefers-color-scheme: dark)`. Returns \"\" when there is nothing to\n * theme. These are brand OVERRIDES on top of a base theme — they do not replace\n * the theme layer the components need (see {@link brandTokens} usage in the docs).\n */\nexport function brandTokens(\n brand: ChapterBrand | undefined,\n options: { selector?: string } = {},\n): string {\n if (!brand) return \"\";\n const light = [...paletteDecls(brand.palette), ...semanticDecls(brand.tokens)];\n const fonts = brand.fonts;\n if (fonts?.display) light.push(`--ui-font-display: ${cleanValue(fonts.display)};`);\n if (fonts?.body) light.push(`--ui-font-sans: ${cleanValue(fonts.body)};`);\n if (fonts?.numeral) light.push(`--ui-font-numeral: ${cleanValue(fonts.numeral)};`);\n const dark = [...paletteDecls(brand.paletteDark), ...semanticDecls(brand.tokensDark)];\n const invert = paletteDecls(brand.paletteInvert);\n const selector = options.selector ?? \":root\";\n const darkSelector = selector === \":root\" ? ':root[data-theme=\"dark\"]' : `${selector}[data-theme=\"dark\"]`;\n const systemSelector = selector === \":root\"\n ? ':root:not([data-theme=\"light\"])'\n : `${selector}:not([data-theme=\"light\"])`;\n\n let css = light.length ? `${selector} {\\n ${light.join(\"\\n \")}\\n}\\n` : \"\";\n if (dark.length) {\n const block = `{\\n ${dark.join(\"\\n \")}\\n}`;\n css += `${darkSelector} ${block}\\n@media (prefers-color-scheme: dark) {\\n ${systemSelector} ${block}\\n}\\n`;\n }\n if (invert.length) {\n const invertSelector = selector === \":root\"\n ? \".ui-invert\"\n : `${selector}.ui-invert, ${selector} .ui-invert`;\n css += `${invertSelector} {\\n color-scheme: dark;\\n ${invert.join(\"\\n \")}\\n}\\n`;\n }\n return css;\n}\n\n/** Structural output accepted from `@odla-ai/brand`'s pure token compiler. */\nexport interface CompiledChapterBrandTokens {\n light: Record<string, string>;\n dark: Record<string, string>;\n}\n\n/**\n * Turn a complete compiled token pair into Chapter's brand contract without\n * coupling Chapter's runtime to the brand-book/agent package. Explicit values in\n * `base` win, so a host can make final per-site adjustments after compilation.\n */\nexport function chapterBrandFromTokens(\n compiled: CompiledChapterBrandTokens,\n base: ChapterBrand = {},\n): ChapterBrand {\n return {\n ...base,\n palette: { ...compiled.light, ...base.palette },\n paletteDark: { ...compiled.dark, ...base.paletteDark },\n paletteInvert: { ...compiled.dark, ...base.paletteInvert },\n };\n}\n","// Client-side convenience for brand tokens: render the chapter's brand as a\n// <style> tag. Prefer emitting brandTokens() into the page <head> at build/SSR\n// time (no flash); use this when that isn't available (e.g. a pure SPA mount).\nimport { brandTokens } from \"../brand.js\";\nimport type { ChapterBrand } from \"../types\";\n\n/** Props for {@link BrandStyle}. */\nexport interface BrandStyleProps {\n brand: ChapterBrand | undefined;\n /** CSS selector receiving the variables. Default `:root`. */\n selector?: string;\n}\n\n/** Render a chapter's brand tokens as an inline <style> block (or nothing when\n * there is no brand to theme). */\nexport function BrandStyle(props: BrandStyleProps) {\n const css = brandTokens(props.brand, { selector: props.selector });\n if (!css) return null;\n return <style>{css}</style>;\n}\n"],"mappings":"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","names":["jsx"]}
|
|
@@ -26,7 +26,7 @@ type TextFields$1<Key extends string> = {
|
|
|
26
26
|
[Field in Key]: string;
|
|
27
27
|
};
|
|
28
28
|
/** User-visible language for sharing, delivery state, and leader rollups. */
|
|
29
|
-
type AdminNetworkCopy = TextFields$1<"title" | "allowlistDescription" | "sharing" | "shareWith" | "shared" | "shareFailed" | "deliveryFailed" | "loadingRollup" | "loadRollupFailed" | "overview" | "configuredFollowers" | "reachableFollowers" | "recordsAcrossFollowers" | "noFollowers" | "followers" | "available" | "unavailable" | "transportReachable" | "transportUnavailable" | "transportNotChecked" | "bindingTransport" | "urlTransport" | "activeConfiguration" | "noActiveConfiguration" | "publicSiteAvailable" | "publicSiteUnavailable" | "publicSiteNotChecked" | "recordTotals" | "noRecordTotals">;
|
|
29
|
+
type AdminNetworkCopy = TextFields$1<"title" | "allowlistDescription" | "sharing" | "shareWith" | "shared" | "shareFailed" | "deliveryFailed" | "loadingRollup" | "loadRollupFailed" | "overview" | "configuredFollowers" | "reachableFollowers" | "recordsAcrossFollowers" | "noFollowers" | "followers" | "available" | "unavailable" | "transportReachable" | "transportUnavailable" | "transportNotChecked" | "edgeUnpaired" | "edgeUnpairedHint" | "bindingTransport" | "urlTransport" | "activeConfiguration" | "noActiveConfiguration" | "publicSiteAvailable" | "publicSiteUnavailable" | "publicSiteNotChecked" | "recordTotals" | "noRecordTotals">;
|
|
30
30
|
|
|
31
31
|
/** Copy for the person record panels: lifecycle actions, comms, scheduling, billing and meetings. */
|
|
32
32
|
type AdminRecordsCopy = {
|
|
@@ -79,7 +79,7 @@ interface ChapterCopy {
|
|
|
79
79
|
availability: TextFields<"loading" | "loadFailed" | "saved" | "title" | "days" | "startHour" | "endHour" | "slotMinutes" | "minNoticeHours" | "windowDays" | "timezone" | "summaryTemplate" | "save"> & {
|
|
80
80
|
dayLabels: readonly string[];
|
|
81
81
|
};
|
|
82
|
-
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">;
|
|
82
|
+
email: TextFields<"loading" | "loadFailed" | "saved" | "sent" | "testFailed" | "delivery" | "notificationAddress" | "replyTo" | "fromName" | "fromNameHint" | "debugInbox" | "debugInboxHint" | "sendTest" | "enabled" | "subject" | "body" | "save" | "sendLog" | "sentColumn" | "templateColumn" | "statusColumn" | "toColumn" | "failed" | "redirected" | "delivered">;
|
|
83
83
|
meetings: TextFields<"loading" | "loadFailed" | "cancelConfirm" | "newStartPrompt" | "parseFailed" | "agenda" | "empty" | "unknown" | "drift" | "meet" | "reschedule" | "cancel">;
|
|
84
84
|
admission: TextFields<"title" | "description" | "email" | "decision" | "expires" | "note" | "waive" | "require" | "add" | "ledger" | "loading" | "consumed" | "revoked" | "expired" | "active" | "local" | "parent" | "revoke">;
|
|
85
85
|
network: AdminNetworkCopy;
|
|
@@ -215,7 +215,10 @@ type DbRules = Record<string, Rule>;
|
|
|
215
215
|
|
|
216
216
|
/** One follower site a leader can push CRM records to. The secret VALUE never
|
|
217
217
|
* lives in config: `secretName` names the leader tenant's vaulted copy of the
|
|
218
|
-
* follower's `network_share_secret`.
|
|
218
|
+
* follower's `network_share_secret`. Both slots are declared by
|
|
219
|
+
* `createChapterIntegration` and filled together by
|
|
220
|
+
* `odla-ai chapter edge pair --leader <leader> --follower <id>`; until then
|
|
221
|
+
* every leader-side surface reports the edge as `edge_unpaired`. */
|
|
219
222
|
interface ChapterNetworkTarget {
|
|
220
223
|
/** Stable lowercase slug used by the UI, audit tags, and default secret name. */
|
|
221
224
|
id: string;
|
|
@@ -460,9 +463,12 @@ interface EmailTemplate {
|
|
|
460
463
|
interface ChapterEmails {
|
|
461
464
|
notificationEmail: string;
|
|
462
465
|
replyTo?: string;
|
|
466
|
+
/** Display name on outbound lifecycle mail (`Tori Horton <from@…>`); the
|
|
467
|
+
* address itself stays the Worker's `EMAIL_FROM`. Absent ⇒ bare address. */
|
|
468
|
+
fromName?: string;
|
|
463
469
|
debugEmail?: string;
|
|
464
|
-
/** Operational lifecycle templates: adminNotification /
|
|
465
|
-
* prepEmail / onboardingInvite, each `{ subject, text, enabled? }`. */
|
|
470
|
+
/** Operational lifecycle templates: adminNotification / submitConfirmation /
|
|
471
|
+
* paymentConfirmation / prepEmail / onboardingInvite, each `{ subject, text, enabled? }`. */
|
|
466
472
|
templates?: Record<string, EmailTemplate>;
|
|
467
473
|
}
|
|
468
474
|
/** Booking rules for the intro-call scheduler (stored as schedulingJson). */
|
package/dist/index.cjs
CHANGED
|
@@ -31,6 +31,7 @@ __export(src_exports, {
|
|
|
31
31
|
CHAPTER_PROMOTION_ADAPTER: () => CHAPTER_PROMOTION_ADAPTER,
|
|
32
32
|
DEFAULT_CHAPTER_COPY: () => DEFAULT_CHAPTER_COPY,
|
|
33
33
|
DEFAULT_SHARE_FIELDS: () => DEFAULT_SHARE_FIELDS,
|
|
34
|
+
EDGE_UNPAIRED: () => EDGE_UNPAIRED,
|
|
34
35
|
GIFT_OFFER_CATALOG_VERSION: () => GIFT_OFFER_CATALOG_VERSION,
|
|
35
36
|
JOURNEY_ENDINGS: () => JOURNEY_ENDINGS,
|
|
36
37
|
JOURNEY_STAGES: () => JOURNEY_STAGES,
|
|
@@ -111,6 +112,7 @@ __export(src_exports, {
|
|
|
111
112
|
defaultCrm: () => defaultCrm,
|
|
112
113
|
defineChapter: () => defineChapter,
|
|
113
114
|
drainMembershipEffects: () => drainMembershipEffects,
|
|
115
|
+
edgeUnpaired: () => edgeUnpaired,
|
|
114
116
|
emailGroupFrom: () => emailGroupFrom,
|
|
115
117
|
endForSlot: () => endForSlot,
|
|
116
118
|
exportChapterPromotion: () => exportChapterPromotion,
|
|
@@ -170,6 +172,7 @@ __export(src_exports, {
|
|
|
170
172
|
normalizeSharedRecord: () => normalizeSharedRecord,
|
|
171
173
|
normalizeWebhookEvent: () => normalizeWebhookEvent,
|
|
172
174
|
offerableTiers: () => offerableTiers,
|
|
175
|
+
pairingCommand: () => pairingCommand,
|
|
173
176
|
paymentsReady: () => paymentsReady,
|
|
174
177
|
personInputFromApp: () => personInputFromApp,
|
|
175
178
|
planDelivery: () => planDelivery,
|
|
@@ -777,7 +780,7 @@ var giftRedemptionAttemptsEntity = { attrs: {
|
|
|
777
780
|
} };
|
|
778
781
|
|
|
779
782
|
// src/payment-recovery-schema.ts
|
|
780
|
-
var field = (type, unique = false) => ({ type, unique, indexed: type !== "json", optional
|
|
783
|
+
var field = (type, unique = false, optional = false) => ({ type, unique, indexed: type !== "json", optional });
|
|
781
784
|
var paymentRecoveryReviewsEntity = { attrs: {
|
|
782
785
|
id: field("string", true),
|
|
783
786
|
eventId: field("string"),
|
|
@@ -786,7 +789,9 @@ var paymentRecoveryReviewsEntity = { attrs: {
|
|
|
786
789
|
previousDigest: field("string"),
|
|
787
790
|
providerDigest: field("string"),
|
|
788
791
|
scope: field("string"),
|
|
789
|
-
previousState: field("json")
|
|
792
|
+
previousState: field("json"),
|
|
793
|
+
action: field("string", false, true),
|
|
794
|
+
reason: field("string", false, true)
|
|
790
795
|
} };
|
|
791
796
|
|
|
792
797
|
// src/schema-stripe.ts
|
|
@@ -982,7 +987,7 @@ var boolean = (flags = {}) => ({
|
|
|
982
987
|
indexed: flags.indexed ?? false,
|
|
983
988
|
optional: false
|
|
984
989
|
});
|
|
985
|
-
var json2 = () => ({ type: "json", unique: false, indexed: false, optional: false });
|
|
990
|
+
var json2 = (flags = {}) => ({ type: "json", unique: false, indexed: false, optional: flags.optional ?? false });
|
|
986
991
|
var id4 = () => string2({ unique: true, indexed: true });
|
|
987
992
|
var events = { attrs: {
|
|
988
993
|
id: id4(),
|
|
@@ -1011,6 +1016,10 @@ var exceptions = { attrs: {
|
|
|
1011
1016
|
status: string2({ indexed: true }),
|
|
1012
1017
|
resolvedDigest: string2({ optional: true }),
|
|
1013
1018
|
incomingDigest: string2({ optional: true }),
|
|
1019
|
+
incomingFact: json2({ optional: true }),
|
|
1020
|
+
reviewedDigest: string2({ optional: true }),
|
|
1021
|
+
resolvedBy: string2({ optional: true }),
|
|
1022
|
+
reviewId: string2({ optional: true }),
|
|
1014
1023
|
detail: json2(),
|
|
1015
1024
|
updatedAt: number2({ indexed: true })
|
|
1016
1025
|
} };
|
|
@@ -1149,6 +1158,7 @@ var groups = {
|
|
|
1149
1158
|
stripePublishableKey: attr7("string", { optional: true }),
|
|
1150
1159
|
notificationEmail: attr7("string"),
|
|
1151
1160
|
replyTo: attr7("string"),
|
|
1161
|
+
fromName: attr7("string", { optional: true }),
|
|
1152
1162
|
debugEmail: attr7("string", { optional: true }),
|
|
1153
1163
|
calendarLink: attr7("string", { optional: true }),
|
|
1154
1164
|
disclaimerText: attr7("string"),
|
|
@@ -1345,6 +1355,13 @@ function defaultCrm(mode) {
|
|
|
1345
1355
|
};
|
|
1346
1356
|
}
|
|
1347
1357
|
|
|
1358
|
+
// src/named-seat-accepted-template.ts
|
|
1359
|
+
var SEAT_ACCEPTED_TEMPLATE = {
|
|
1360
|
+
enabled: true,
|
|
1361
|
+
subject: "{{recipientName}} accepted your {{chapterName}} seat",
|
|
1362
|
+
text: "Hi {{firstName}},\n\n{{recipientName}} has accepted the {{tierName}} seat you purchased at {{chapterName}}. They will complete their own application and any membership requirements from here; nothing more is needed from you.\n\nWarmly,\n{{chapterName}}"
|
|
1363
|
+
};
|
|
1364
|
+
|
|
1348
1365
|
// src/group.ts
|
|
1349
1366
|
var DEFAULT_SCHEDULING = {
|
|
1350
1367
|
slotMinutes: 45,
|
|
@@ -1366,7 +1383,16 @@ ${name}`;
|
|
|
1366
1383
|
text: `A new application came in for ${name}.
|
|
1367
1384
|
|
|
1368
1385
|
Name: {{firstName}} {{lastName}}
|
|
1369
|
-
Email: {{email}}
|
|
1386
|
+
Email: {{email}}
|
|
1387
|
+
Tier: {{tier}}
|
|
1388
|
+
|
|
1389
|
+
Review it: {{adminUrl}}`
|
|
1390
|
+
},
|
|
1391
|
+
submitConfirmation: {
|
|
1392
|
+
subject: `We received your application to ${name}`,
|
|
1393
|
+
text: `Hi {{firstName}},
|
|
1394
|
+
|
|
1395
|
+
Thanks for applying to ${name}. {{submitNextStep}}${sign}`
|
|
1370
1396
|
},
|
|
1371
1397
|
paymentConfirmation: {
|
|
1372
1398
|
subject: `Welcome to ${name}`,
|
|
@@ -1378,14 +1404,17 @@ Your membership payment{{paymentAmountSummary}} is confirmed. {{paymentNextStep}
|
|
|
1378
1404
|
subject: `Your ${name} intro call`,
|
|
1379
1405
|
text: `Hi {{firstName}},
|
|
1380
1406
|
|
|
1381
|
-
Looking forward to our call
|
|
1407
|
+
You are all set{{paymentAmountSummary}}. Looking forward to our call on {{startAt}}.
|
|
1408
|
+
|
|
1409
|
+
Join here: {{meetingLink}}${sign}`
|
|
1382
1410
|
},
|
|
1383
1411
|
onboardingInvite: {
|
|
1384
1412
|
subject: `You're in, ${name}`,
|
|
1385
1413
|
text: `Hi {{firstName}},
|
|
1386
1414
|
|
|
1387
1415
|
Welcome to ${name}. Your member area is here: {{membersUrl}}${sign}`
|
|
1388
|
-
}
|
|
1416
|
+
},
|
|
1417
|
+
seatAccepted: SEAT_ACCEPTED_TEMPLATE
|
|
1389
1418
|
};
|
|
1390
1419
|
}
|
|
1391
1420
|
function buildGroupSeed(config) {
|
|
@@ -1414,6 +1443,7 @@ function buildGroupSeed(config) {
|
|
|
1414
1443
|
schedulingJson: scheduling
|
|
1415
1444
|
};
|
|
1416
1445
|
if (emails?.debugEmail) row2.debugEmail = emails.debugEmail;
|
|
1446
|
+
if (emails?.fromName?.trim()) row2.fromName = emails.fromName.trim();
|
|
1417
1447
|
if (policy.commitmentText) row2.commitmentText = policy.commitmentText;
|
|
1418
1448
|
if (policy.normsText) row2.normsText = policy.normsText;
|
|
1419
1449
|
return row2;
|
|
@@ -1634,6 +1664,8 @@ var DEFAULT_ADMIN_COPY = {
|
|
|
1634
1664
|
delivery: "Delivery",
|
|
1635
1665
|
notificationAddress: "Notification address",
|
|
1636
1666
|
replyTo: "Reply-to",
|
|
1667
|
+
fromName: "From name",
|
|
1668
|
+
fromNameHint: "Shown as the sender; the address stays the Worker's configured one.",
|
|
1637
1669
|
debugInbox: "Debug inbox",
|
|
1638
1670
|
debugInboxHint: "Non-production sends redirect here.",
|
|
1639
1671
|
sendTest: "Send test",
|
|
@@ -1705,6 +1737,8 @@ var DEFAULT_ADMIN_COPY = {
|
|
|
1705
1737
|
transportReachable: "transport reachable",
|
|
1706
1738
|
transportUnavailable: "transport unavailable",
|
|
1707
1739
|
transportNotChecked: "transport not checked",
|
|
1740
|
+
edgeUnpaired: "edge unpaired",
|
|
1741
|
+
edgeUnpairedHint: "This follower's shared secret is not in the leader vault, so nothing can be signed. Pair both vaults from an enrolled machine:",
|
|
1708
1742
|
bindingTransport: "service binding {binding}",
|
|
1709
1743
|
urlTransport: "public URL transport",
|
|
1710
1744
|
activeConfiguration: "active configuration r{revision}",
|
|
@@ -2433,6 +2467,10 @@ function defineChapter(config) {
|
|
|
2433
2467
|
if (!config.emails || typeof config.emails.notificationEmail !== "string" || config.emails.notificationEmail === "") {
|
|
2434
2468
|
throw new Error("defineChapter.emails.notificationEmail: required in chapter mode");
|
|
2435
2469
|
}
|
|
2470
|
+
const fromName = config.emails.fromName;
|
|
2471
|
+
if (fromName !== void 0 && (typeof fromName !== "string" || /[\r\n<>]/.test(fromName) || fromName.trim().length > 100)) {
|
|
2472
|
+
throw new Error("defineChapter.emails.fromName: must be one line of at most 100 characters with no angle brackets");
|
|
2473
|
+
}
|
|
2436
2474
|
if (!config.prices || typeof config.prices.standardCents !== "number") {
|
|
2437
2475
|
throw new Error("defineChapter.prices.standardCents: required in chapter mode");
|
|
2438
2476
|
}
|
|
@@ -3428,6 +3466,7 @@ function createChapterIntegration(chapter, options = {}) {
|
|
|
3428
3466
|
description: "Signing secret for the read-only network commercial Stripe webhook endpoint.",
|
|
3429
3467
|
required: true
|
|
3430
3468
|
});
|
|
3469
|
+
secrets.push(...federationEdgeSecrets(chapter));
|
|
3431
3470
|
return {
|
|
3432
3471
|
id: "chapter",
|
|
3433
3472
|
title: `Chapter: ${chapter.name}`,
|
|
@@ -3471,6 +3510,41 @@ function createChapterIntegration(chapter, options = {}) {
|
|
|
3471
3510
|
runbooks
|
|
3472
3511
|
};
|
|
3473
3512
|
}
|
|
3513
|
+
function federationEdgeSecrets(chapter) {
|
|
3514
|
+
const secrets = chapter.network.targets.map((target) => ({
|
|
3515
|
+
name: target.secretName,
|
|
3516
|
+
description: `Shared secret for the signed federation edge to follower "${target.id}"; pair both vaults with "odla-ai chapter edge pair --leader ${chapter.id} --follower ${target.id}".`,
|
|
3517
|
+
required: true,
|
|
3518
|
+
edge: { role: "leader", peers: [target.id] }
|
|
3519
|
+
}));
|
|
3520
|
+
const peers = [.../* @__PURE__ */ new Set([
|
|
3521
|
+
...chapter.network.readers.map((reader) => reader.id),
|
|
3522
|
+
...(chapter.builderLibrary?.sources ?? []).map((source) => source.sourceId)
|
|
3523
|
+
])];
|
|
3524
|
+
if (peers.length > 0) secrets.push({
|
|
3525
|
+
name: "network_share_secret",
|
|
3526
|
+
description: `Shared secret every declared leader (${peers.join(", ")}) signs federation requests with; pair it with "odla-ai chapter edge pair --leader <leader> --follower ${chapter.id}".`,
|
|
3527
|
+
required: true,
|
|
3528
|
+
edge: { role: "follower", peers }
|
|
3529
|
+
});
|
|
3530
|
+
return secrets;
|
|
3531
|
+
}
|
|
3532
|
+
|
|
3533
|
+
// src/network-edge.ts
|
|
3534
|
+
var EDGE_UNPAIRED = "edge_unpaired";
|
|
3535
|
+
function pairingCommand(leaderId, targetId, env) {
|
|
3536
|
+
const scope2 = env ? ` --env ${env}` : "";
|
|
3537
|
+
return `odla-ai chapter edge pair --leader ${leaderId} --follower ${targetId}${scope2}`;
|
|
3538
|
+
}
|
|
3539
|
+
function edgeUnpaired(leaderId, target, env) {
|
|
3540
|
+
return {
|
|
3541
|
+
code: EDGE_UNPAIRED,
|
|
3542
|
+
target: target.id,
|
|
3543
|
+
secretName: target.secretName,
|
|
3544
|
+
message: `edge to "${target.id}" is unpaired: leader vault slot "${target.secretName}" is empty`,
|
|
3545
|
+
hint: pairingCommand(leaderId, target.id, env)
|
|
3546
|
+
};
|
|
3547
|
+
}
|
|
3474
3548
|
|
|
3475
3549
|
// src/membership-policy.ts
|
|
3476
3550
|
function seatPurchaseDeadline(offering, member) {
|
|
@@ -6840,6 +6914,12 @@ function planDelivery(input) {
|
|
|
6840
6914
|
}
|
|
6841
6915
|
|
|
6842
6916
|
// src/notify.ts
|
|
6917
|
+
function fromAddress(configured, name) {
|
|
6918
|
+
const display = name?.trim();
|
|
6919
|
+
if (!display) return configured;
|
|
6920
|
+
const angled = configured.match(/<([^>]+)>/);
|
|
6921
|
+
return { name: display, email: (angled ? angled[1] : configured).trim() };
|
|
6922
|
+
}
|
|
6843
6923
|
async function sendTemplated(deps, input) {
|
|
6844
6924
|
const { emailLog: emailLog2 } = await deps.db.query({ emailLog: { $: { where: { dedupeKey: input.dedupeKey } } } });
|
|
6845
6925
|
const prior = Array.isArray(emailLog2) ? emailLog2 : [];
|
|
@@ -6902,7 +6982,7 @@ async function sendTemplated(deps, input) {
|
|
|
6902
6982
|
let messageId;
|
|
6903
6983
|
try {
|
|
6904
6984
|
const response = await deps.sender.send({
|
|
6905
|
-
from: deps.from,
|
|
6985
|
+
from: fromAddress(deps.from, input.group.fromName),
|
|
6906
6986
|
to: [decision.to],
|
|
6907
6987
|
subject: String(row2.subject),
|
|
6908
6988
|
text: String(row2.body),
|
|
@@ -6935,6 +7015,7 @@ function emailGroupFrom(row2) {
|
|
|
6935
7015
|
name: String(row2.name ?? ""),
|
|
6936
7016
|
replyTo: str2(row2.replyTo) ?? "",
|
|
6937
7017
|
debugEmail: str2(row2.debugEmail),
|
|
7018
|
+
fromName: str2(row2.fromName),
|
|
6938
7019
|
refundPolicyText: str2(row2.refundPolicyText),
|
|
6939
7020
|
commitmentText: str2(row2.commitmentText),
|
|
6940
7021
|
normsText: str2(row2.normsText),
|