@odla-ai/chapter 0.21.0 → 0.22.1

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.
@@ -50,8 +50,22 @@ interface ChapterBrandTokens {
50
50
  danger?: string;
51
51
  chart1?: string;
52
52
  chart2?: string;
53
+ chart3?: string;
54
+ chart4?: string;
55
+ chart5?: string;
56
+ chart6?: string;
53
57
  chartPositive?: string;
54
58
  chartNegative?: string;
59
+ /** Admin/application content width, e.g. `"960px"`. */
60
+ contentWidth?: string;
61
+ panelRadius?: string;
62
+ panelShadow?: string;
63
+ masterDetailColumns?: string;
64
+ masterDetailMinHeight?: string;
65
+ /** Chapter-admin page padding shorthand. */
66
+ pagePadding?: string;
67
+ /** Chapter-admin workspace-tab padding shorthand. */
68
+ workspacePadding?: string;
55
69
  }
56
70
  /** Brand tokens that theme the site: theme, semantic roles, fonts, and logos. */
57
71
  interface ChapterBrand {
@@ -1,13 +1,42 @@
1
- import * as react from 'react';
2
- import { ReactNode } from 'react';
1
+ import * as preact from 'preact';
2
+ import { ComponentChildren, JSX } from 'preact';
3
3
  import { CrmClient, Crm, CrmRecord } from '@odla-ai/crm';
4
- import { C as Chapter } from '../../types-CXDiDj_s.js';
5
- import { RecordPanelTab, CrmWorkspaceRecordContext } from '@odla-ai/crm/ui';
4
+ import { C as Chapter } from '../../types-CgWUQ2MH.js';
5
+ import { CrmLifecycleAdapter, CrmWorkspaceMasterContext, CrmWorkspaceRecordContext, RecordPanelTab } from '@odla-ai/crm/ui';
6
+
7
+ /** Normalized public auth configuration consumed by the Clerk gate. */
8
+ interface ChapterAdminAuthConfig {
9
+ publishableKey: string | null;
10
+ }
11
+ /** Normalized current-user data retained for workspace visibility decisions. */
12
+ interface ChapterAdminUser extends Record<string, unknown> {
13
+ authorized?: boolean;
14
+ email?: string | null;
15
+ }
16
+ /** Inputs supplied to custom admin configuration and current-user loaders. */
17
+ interface ChapterAdminAuthLoadContext {
18
+ apiBase: string;
19
+ getToken?: () => Promise<string | null>;
20
+ }
21
+ /** Existing-site adapter for auth endpoint names and response shapes. */
22
+ interface ChapterAdminAuthAdapter {
23
+ configPath?: string;
24
+ currentUserPath?: string;
25
+ loadConfig?: (context: ChapterAdminAuthLoadContext) => Promise<ChapterAdminAuthConfig>;
26
+ loadCurrentUser?: (context: Required<ChapterAdminAuthLoadContext>) => Promise<ChapterAdminUser>;
27
+ mapConfig?: (body: Record<string, unknown>) => ChapterAdminAuthConfig;
28
+ mapCurrentUser?: (body: Record<string, unknown>) => ChapterAdminUser;
29
+ isAuthorized?: (user: ChapterAdminUser) => boolean;
30
+ }
31
+ /** Load and normalize the public authentication configuration. */
32
+ declare function loadChapterAdminConfig(adapter: ChapterAdminAuthAdapter, apiBase: string): Promise<ChapterAdminAuthConfig>;
33
+ /** Load and normalize the authenticated user used by admin workspaces. */
34
+ declare function loadChapterAdminUser(adapter: ChapterAdminAuthAdapter, apiBase: string, getToken: () => Promise<string | null>): Promise<ChapterAdminUser>;
6
35
 
7
36
  type Brand = {
8
37
  name: string;
9
38
  badge?: string;
10
- wordmark?: ReactNode;
39
+ wordmark?: ComponentChildren;
11
40
  };
12
41
 
13
42
  /** Typed URL state for the admin's nested in-page workspaces. */
@@ -40,6 +69,7 @@ type AdminRouteTarget = string | {
40
69
  interface AdminSectionContext {
41
70
  client: CrmClient;
42
71
  getToken: () => Promise<string | null>;
72
+ currentUser?: Record<string, unknown>;
43
73
  route: AdminRouteState;
44
74
  navigate: (target: AdminRouteTarget) => void;
45
75
  href: (target: AdminRouteTarget) => string;
@@ -48,12 +78,28 @@ interface AdminSectionContext {
48
78
  interface AdminWorkspace {
49
79
  id: string;
50
80
  label: string;
51
- render: (ctx: AdminSectionContext) => ReactNode;
81
+ /** Canonical nested view used when a workspace is opened without one. */
82
+ defaultViewId?: string;
83
+ render: (ctx: AdminSectionContext) => ComponentChildren;
52
84
  }
53
85
  /** Legacy name for a top-level workspace. @deprecated Use AdminWorkspace. */
54
86
  type AdminSection = AdminWorkspace;
55
- /** Built-in shell presentation: editorial, legacy top bar, or host-owned. */
56
- type AdminChrome = "editorial" | "topbar" | "none";
87
+ /** Built-in shell presentation; embedded preserves site-owned chrome. */
88
+ type AdminChrome = "embedded" | "standalone" | "editorial" | "topbar" | "none";
89
+ /** Identity and sign-out services supplied to host-owned account controls. */
90
+ interface AdminAccountMenuProps {
91
+ brand: Brand;
92
+ email: string | null;
93
+ currentUser?: Record<string, unknown>;
94
+ signOut: () => void;
95
+ }
96
+ /** Workspace route and navigation services supplied to host-owned navigation. */
97
+ interface AdminWorkspaceNavProps {
98
+ workspaces: readonly AdminWorkspace[];
99
+ route: AdminRouteState;
100
+ navigate: (target: AdminRouteTarget) => void;
101
+ href: (target: AdminRouteTarget) => string;
102
+ }
57
103
  interface AdminShellProps {
58
104
  workspaces: AdminWorkspace[];
59
105
  basePath: string;
@@ -62,13 +108,12 @@ interface AdminShellProps {
62
108
  getToken: () => Promise<string | null>;
63
109
  signOut: () => void;
64
110
  email: string | null;
111
+ currentUser?: Record<string, unknown>;
65
112
  routing?: AdminRouting;
66
113
  chrome?: AdminChrome;
67
- renderHeader?: (props: {
68
- brand: Brand;
69
- email: string | null;
70
- signOut: () => void;
71
- }) => ReactNode;
114
+ renderHeader?: (props: AdminAccountMenuProps) => ComponentChildren;
115
+ renderAccountMenu?: (props: AdminAccountMenuProps) => ComponentChildren;
116
+ renderWorkspaceNav?: (props: AdminWorkspaceNavProps) => ComponentChildren;
72
117
  }
73
118
 
74
119
  /** Configuration for the Clerk-gated, brand-scoped Chapter admin console. */
@@ -84,12 +129,16 @@ interface ChapterAdminProps {
84
129
  apiBase?: string;
85
130
  /** Default fragment: `/admin/#people/person/record-id/profile`. */
86
131
  routing?: AdminRouting;
87
- /** Default editorial. Topbar retains the legacy global-nav shell. */
132
+ /** Default embedded. Standalone adds packaged global chrome. */
88
133
  chrome?: AdminChrome;
89
134
  renderHeader?: AdminShellProps["renderHeader"];
135
+ renderAccountMenu?: (props: AdminAccountMenuProps) => ComponentChildren;
136
+ renderWorkspaceNav?: (props: AdminWorkspaceNavProps) => ComponentChildren;
137
+ /** Adapt existing config/current-user endpoints without rewriting auth. */
138
+ auth?: ChapterAdminAuthAdapter;
90
139
  }
91
140
  /** Render the complete authenticated admin application from one Chapter config. */
92
- declare function ChapterAdmin(props: ChapterAdminProps): react.JSX.Element;
141
+ declare function ChapterAdmin(props: ChapterAdminProps): preact.JSX.Element;
93
142
 
94
143
  /** Default follower grammar: three workspaces with nested operational views. */
95
144
  declare function defaultAdminWorkspaces(chapter: Chapter): AdminWorkspace[];
@@ -97,6 +146,9 @@ declare function defaultAdminWorkspaces(chapter: Chapter): AdminWorkspace[];
97
146
  /** Legacy default catalog. @deprecated Use defaultAdminWorkspaces. */
98
147
  declare function defaultAdminSections(chapter: Chapter): AdminSection[];
99
148
 
149
+ /** Application-authoritative stage transitions used by Chapter collections. */
150
+ declare function applicationLifecycleAdapter(ctx: Pick<AdminSectionContext, "getToken">): CrmLifecycleAdapter;
151
+
100
152
  /** Configuration for the person-collection compatibility preset. */
101
153
  interface PeopleSectionOptions {
102
154
  crm: Crm;
@@ -113,6 +165,13 @@ interface CollectionSectionOptions {
113
165
  lifecycle?: boolean;
114
166
  roles?: readonly string[];
115
167
  networkSharing?: boolean;
168
+ lifecycleAdapter?: (CrmLifecycleAdapter | ((ctx: AdminSectionContext) => CrmLifecycleAdapter));
169
+ requireLifecycleAdapter?: boolean;
170
+ renderSummary?: (context: CrmWorkspaceMasterContext, ctx: AdminSectionContext) => ComponentChildren;
171
+ renderMaster?: (context: CrmWorkspaceMasterContext, ctx: AdminSectionContext) => ComponentChildren;
172
+ renderDetailHeader?: (context: CrmWorkspaceRecordContext, ctx: AdminSectionContext) => ComponentChildren;
173
+ renderEmptyDetail?: (context: CrmWorkspaceMasterContext, ctx: AdminSectionContext) => ComponentChildren;
174
+ hrefForRecord?: (record: CrmRecord, ctx: AdminSectionContext) => string;
116
175
  extendRecordTabs?: (tabs: readonly RecordPanelTab[], context: CrmWorkspaceRecordContext) => readonly RecordPanelTab[];
117
176
  }
118
177
  /** Build one CRM collection as an admin workspace. */
@@ -121,6 +180,13 @@ declare function collectionSection(options: CollectionSectionOptions): AdminSect
121
180
  declare function peopleSection(options: PeopleSectionOptions & {
122
181
  roles?: readonly string[];
123
182
  networkSharing?: boolean;
183
+ lifecycleAdapter?: CollectionSectionOptions["lifecycleAdapter"];
184
+ requireLifecycleAdapter?: boolean;
185
+ renderSummary?: CollectionSectionOptions["renderSummary"];
186
+ renderMaster?: CollectionSectionOptions["renderMaster"];
187
+ renderDetailHeader?: CollectionSectionOptions["renderDetailHeader"];
188
+ renderEmptyDetail?: CollectionSectionOptions["renderEmptyDetail"];
189
+ hrefForRecord?: CollectionSectionOptions["hrefForRecord"];
124
190
  extendRecordTabs?: CollectionSectionOptions["extendRecordTabs"];
125
191
  }): AdminSection;
126
192
 
@@ -179,7 +245,7 @@ interface RecordActionsProps {
179
245
  }
180
246
  /** The role/approve/refund controls for one person record. Renders only the
181
247
  * actions the record supports. */
182
- declare function RecordActions({ getToken, record, roles, onChanged }: RecordActionsProps): react.JSX.Element | null;
248
+ declare function RecordActions({ getToken, record, roles, onChanged }: RecordActionsProps): preact.JSX.Element | null;
183
249
 
184
250
  /** Props for {@link NetworkShareActions}. */
185
251
  interface NetworkShareActionsProps {
@@ -189,7 +255,7 @@ interface NetworkShareActionsProps {
189
255
  }
190
256
  /** Render follower delivery buttons when this chapter configured network
191
257
  * targets. Targets with an explicit type allowlist hide incompatible records. */
192
- declare function NetworkShareActions(props: NetworkShareActionsProps): react.JSX.Element | null;
258
+ declare function NetworkShareActions(props: NetworkShareActionsProps): preact.JSX.Element | null;
193
259
 
194
260
  /** Fetch a JSON `/api/admin/*` route with the admin bearer token. Throws on a
195
261
  * non-2xx (the body's `error`, else the status). */
@@ -202,25 +268,25 @@ interface AdminResource<T> {
202
268
  /** Re-fetch (e.g. after a mutation). */
203
269
  refresh: () => void;
204
270
  }
205
- /** Load a `/api/admin/*` GET route into React state, re-fetching on `refresh()`
271
+ /** Load a `/api/admin/*` GET route into Preact state, re-fetching on `refresh()`
206
272
  * or when `path` changes. */
207
273
  declare function useAdminResource<T = unknown>(getToken: () => Promise<string | null>, path: string): AdminResource<T>;
208
274
 
209
275
  /** The centered content column every admin section renders into. */
210
276
  declare function AdminPage({ children }: {
211
- children: ReactNode;
212
- }): react.JSX.Element;
277
+ children: ComponentChildren;
278
+ }): JSX.Element;
213
279
  /** An odla-ui `.panel` card with an optional heading + trailing actions. */
214
280
  declare function Panel({ title, actions, children }: {
215
- title?: ReactNode;
216
- actions?: ReactNode;
217
- children: ReactNode;
218
- }): react.JSX.Element;
281
+ title?: ComponentChildren;
282
+ actions?: ComponentChildren;
283
+ children: ComponentChildren;
284
+ }): JSX.Element;
219
285
  /** A loud banner when the odla-ui theme token layer is missing. */
220
- declare function ThemeWarning(): react.JSX.Element | null;
286
+ declare function ThemeWarning(): JSX.Element | null;
221
287
  /** A centered status line for loading / empty / error states. */
222
288
  declare function AdminNote({ children }: {
223
- children: ReactNode;
224
- }): react.JSX.Element;
289
+ children: ComponentChildren;
290
+ }): JSX.Element;
225
291
 
226
- export { type AdminChrome, AdminNote, AdminPage, type AdminResource, type AdminRouteState, type AdminRouteTarget, type AdminRouting, type AdminSection, type AdminSectionContext, type AdminWorkspace, type AvailabilitySectionOptions, type BillingSectionOptions, ChapterAdmin, type ChapterAdminProps, type CollectionSectionOptions, type DashboardSectionOptions, type EmailSectionOptions, type MeetingsSectionOptions, NetworkShareActions, type NetworkShareActionsProps, Panel, type PeopleSectionOptions, RecordActions, type RecordActionsProps, ThemeWarning, adminFetch, adminRouteFromUrl, adminRouteHref, adminSectionFromUrl, adminSectionHref, availabilitySection, billingSection, collectionSection, dashboardSection, defaultAdminSections, defaultAdminWorkspaces, emailSection, meetingsSection, peopleSection, useAdminResource };
292
+ export { type AdminAccountMenuProps, type AdminChrome, AdminNote, AdminPage, type AdminResource, type AdminRouteState, type AdminRouteTarget, type AdminRouting, type AdminSection, type AdminSectionContext, type AdminWorkspace, type AdminWorkspaceNavProps, type AvailabilitySectionOptions, type BillingSectionOptions, ChapterAdmin, type ChapterAdminAuthAdapter, type ChapterAdminAuthConfig, type ChapterAdminAuthLoadContext, type ChapterAdminProps, type ChapterAdminUser, type CollectionSectionOptions, type DashboardSectionOptions, type EmailSectionOptions, type MeetingsSectionOptions, NetworkShareActions, type NetworkShareActionsProps, Panel, type PeopleSectionOptions, RecordActions, type RecordActionsProps, ThemeWarning, adminFetch, adminRouteFromUrl, adminRouteHref, adminSectionFromUrl, adminSectionHref, applicationLifecycleAdapter, availabilitySection, billingSection, collectionSection, dashboardSection, defaultAdminSections, defaultAdminWorkspaces, emailSection, loadChapterAdminConfig, loadChapterAdminUser, meetingsSection, peopleSection, useAdminResource };
@@ -11,6 +11,7 @@ import {
11
11
  adminRouteHref,
12
12
  adminSectionFromUrl,
13
13
  adminSectionHref,
14
+ applicationLifecycleAdapter,
14
15
  availabilitySection,
15
16
  billingSection,
16
17
  collectionSection,
@@ -18,11 +19,13 @@ import {
18
19
  defaultAdminSections,
19
20
  defaultAdminWorkspaces,
20
21
  emailSection,
22
+ loadChapterAdminConfig,
23
+ loadChapterAdminUser,
21
24
  meetingsSection,
22
25
  peopleSection,
23
26
  useAdminResource
24
- } from "../../chunk-AOTWFOH4.js";
25
- import "../../chunk-3JG5X2LT.js";
27
+ } from "../../chunk-C4XQSRW7.js";
28
+ import "../../chunk-3QKGKUTX.js";
26
29
  export {
27
30
  AdminNote,
28
31
  AdminPage,
@@ -36,6 +39,7 @@ export {
36
39
  adminRouteHref,
37
40
  adminSectionFromUrl,
38
41
  adminSectionHref,
42
+ applicationLifecycleAdapter,
39
43
  availabilitySection,
40
44
  billingSection,
41
45
  collectionSection,
@@ -43,6 +47,8 @@ export {
43
47
  defaultAdminSections,
44
48
  defaultAdminWorkspaces,
45
49
  emailSection,
50
+ loadChapterAdminConfig,
51
+ loadChapterAdminUser,
46
52
  meetingsSection,
47
53
  peopleSection,
48
54
  useAdminResource
@@ -1,6 +1,6 @@
1
- export { AdminChrome, AdminNote, AdminPage, AdminResource, AdminRouteState, AdminRouteTarget, AdminRouting, AdminSection, AdminSectionContext, AdminWorkspace, AvailabilitySectionOptions, BillingSectionOptions, ChapterAdmin, ChapterAdminProps, CollectionSectionOptions, DashboardSectionOptions, EmailSectionOptions, MeetingsSectionOptions, NetworkShareActions, NetworkShareActionsProps, Panel, PeopleSectionOptions, RecordActions, RecordActionsProps, ThemeWarning, adminFetch, adminRouteFromUrl, adminRouteHref, adminSectionFromUrl, adminSectionHref, availabilitySection, billingSection, collectionSection, dashboardSection, defaultAdminSections, defaultAdminWorkspaces, emailSection, meetingsSection, peopleSection, useAdminResource } from './admin/index.js';
1
+ export { AdminAccountMenuProps, AdminChrome, AdminNote, AdminPage, AdminResource, AdminRouteState, AdminRouteTarget, AdminRouting, AdminSection, AdminSectionContext, AdminWorkspace, AdminWorkspaceNavProps, AvailabilitySectionOptions, BillingSectionOptions, ChapterAdmin, ChapterAdminAuthAdapter, ChapterAdminAuthConfig, ChapterAdminAuthLoadContext, ChapterAdminProps, ChapterAdminUser, CollectionSectionOptions, DashboardSectionOptions, EmailSectionOptions, MeetingsSectionOptions, NetworkShareActions, NetworkShareActionsProps, Panel, PeopleSectionOptions, RecordActions, RecordActionsProps, ThemeWarning, adminFetch, adminRouteFromUrl, adminRouteHref, adminSectionFromUrl, adminSectionHref, applicationLifecycleAdapter, availabilitySection, billingSection, collectionSection, dashboardSection, defaultAdminSections, defaultAdminWorkspaces, emailSection, loadChapterAdminConfig, loadChapterAdminUser, meetingsSection, peopleSection, useAdminResource } from './admin/index.js';
2
2
  export { ApiFn, BrandStyle, BrandStyleProps, JoinConfig, JoinIsland, JoinIslandProps, MembersArea, MembersAreaProps, PaymentStep, PaymentStepProps, RescheduleProps, Rescheduler, Slot, SlotPicker, SlotPickerClasses, SlotPickerProps, dayKey, dayLabel, fmtDate, fmtMoney, fullLabel, groupSlotsByDay, timeLabel, tzShort } from './member/index.js';
3
- import 'react';
3
+ import 'preact';
4
4
  import '@odla-ai/crm';
5
- import '../types-CXDiDj_s.js';
5
+ import '../types-CgWUQ2MH.js';
6
6
  import '@odla-ai/crm/ui';
package/dist/ui/index.js CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  groupSlotsByDay,
13
13
  timeLabel,
14
14
  tzShort
15
- } from "../chunk-ILGUJXSB.js";
15
+ } from "../chunk-4COPR4SY.js";
16
16
  import {
17
17
  AdminNote,
18
18
  AdminPage,
@@ -26,6 +26,7 @@ import {
26
26
  adminRouteHref,
27
27
  adminSectionFromUrl,
28
28
  adminSectionHref,
29
+ applicationLifecycleAdapter,
29
30
  availabilitySection,
30
31
  billingSection,
31
32
  collectionSection,
@@ -33,13 +34,15 @@ import {
33
34
  defaultAdminSections,
34
35
  defaultAdminWorkspaces,
35
36
  emailSection,
37
+ loadChapterAdminConfig,
38
+ loadChapterAdminUser,
36
39
  meetingsSection,
37
40
  peopleSection,
38
41
  useAdminResource
39
- } from "../chunk-AOTWFOH4.js";
42
+ } from "../chunk-C4XQSRW7.js";
40
43
  import {
41
44
  BrandStyle
42
- } from "../chunk-3JG5X2LT.js";
45
+ } from "../chunk-3QKGKUTX.js";
43
46
  export {
44
47
  AdminNote,
45
48
  AdminPage,
@@ -59,6 +62,7 @@ export {
59
62
  adminRouteHref,
60
63
  adminSectionFromUrl,
61
64
  adminSectionHref,
65
+ applicationLifecycleAdapter,
62
66
  availabilitySection,
63
67
  billingSection,
64
68
  collectionSection,
@@ -72,6 +76,8 @@ export {
72
76
  fmtMoney,
73
77
  fullLabel,
74
78
  groupSlotsByDay,
79
+ loadChapterAdminConfig,
80
+ loadChapterAdminUser,
75
81
  meetingsSection,
76
82
  peopleSection,
77
83
  timeLabel,
@@ -1,6 +1,6 @@
1
- import * as react from 'react';
2
- import { ReactNode } from 'react';
3
- import { a as ChapterBrand } from '../../types-CXDiDj_s.js';
1
+ import * as preact from 'preact';
2
+ import { ComponentChildren, JSX } from 'preact';
3
+ import { a as ChapterBrand } from '../../types-CgWUQ2MH.js';
4
4
  import '@odla-ai/crm';
5
5
 
6
6
  /** A bookable slot: a start instant in epoch milliseconds. */
@@ -54,7 +54,7 @@ interface SlotPickerProps<T extends Slot = Slot> {
54
54
  /** A day-chips + time-grid slot picker. Presentational and `onPick`-driven: it
55
55
  * owns only the active-day selection; the caller owns slot data, the chosen
56
56
  * slot, and booking. Shared by the join booking step and the member reschedule. */
57
- declare function SlotPicker<T extends Slot = Slot>(props: SlotPickerProps<T>): react.JSX.Element;
57
+ declare function SlotPicker<T extends Slot = Slot>(props: SlotPickerProps<T>): preact.JSX.Element;
58
58
 
59
59
  /** Issue an authenticated request and parse the JSON response. Throws on a
60
60
  * non-2xx status (the islands catch and surface a friendly message). */
@@ -70,11 +70,11 @@ interface MembersAreaProps {
70
70
  /** Where an accountless member goes to apply. Default "/join.html". */
71
71
  applyHref?: string;
72
72
  /** What a full (non-provisional) member sees below the account header. */
73
- memberContent?: ReactNode;
73
+ memberContent?: ComponentChildren;
74
74
  }
75
75
  /** The signed-in member area. Loads /api/me on mount and renders the account
76
76
  * header plus the provisional application card or the full-member content. */
77
- declare function MembersArea(props: MembersAreaProps): react.JSX.Element;
77
+ declare function MembersArea(props: MembersAreaProps): preact.JSX.Element;
78
78
 
79
79
  /** Props for {@link Rescheduler}. */
80
80
  interface RescheduleProps {
@@ -88,7 +88,7 @@ interface RescheduleProps {
88
88
  }
89
89
  /** A collapsed "change your time" link that expands into a {@link SlotPicker} and
90
90
  * rebooks on pick. Degrades to a message when scheduling is unavailable. */
91
- declare function Rescheduler(props: RescheduleProps): react.JSX.Element;
91
+ declare function Rescheduler(props: RescheduleProps): preact.JSX.Element;
92
92
 
93
93
  /** The public join config (the shape `GET /api/join-config` returns). */
94
94
  interface JoinConfig {
@@ -102,13 +102,13 @@ interface JoinIslandProps {
102
102
  config: JoinConfig;
103
103
  /** The site's application form fields — inputs with `name` attributes; their
104
104
  * values are collected via FormData and posted to /api/applications. */
105
- children: ReactNode;
105
+ children: ComponentChildren;
106
106
  /** Where the confirmation links after booking. Default "/members/". */
107
107
  membersHref?: string;
108
108
  }
109
109
  /** The signup island. Renders the site's form, then drives payment (when the
110
110
  * chapter charges) and booking (when a calendar is connected) to confirmation. */
111
- declare function JoinIsland(props: JoinIslandProps): react.JSX.Element;
111
+ declare function JoinIsland(props: JoinIslandProps): JSX.Element;
112
112
 
113
113
  /** Props for {@link PaymentStep}. */
114
114
  interface PaymentStepProps {
@@ -121,7 +121,7 @@ interface PaymentStepProps {
121
121
  }
122
122
  /** The card-entry step: acknowledge the refund policy → create the subscription
123
123
  * → mount Stripe Elements → confirm. */
124
- declare function PaymentStep(props: PaymentStepProps): react.JSX.Element;
124
+ declare function PaymentStep(props: PaymentStepProps): preact.JSX.Element;
125
125
 
126
126
  /** Props for {@link BrandStyle}. */
127
127
  interface BrandStyleProps {
@@ -131,6 +131,6 @@ interface BrandStyleProps {
131
131
  }
132
132
  /** Render a chapter's brand tokens as an inline <style> block (or nothing when
133
133
  * there is no brand to theme). */
134
- declare function BrandStyle(props: BrandStyleProps): react.JSX.Element | null;
134
+ declare function BrandStyle(props: BrandStyleProps): preact.JSX.Element | null;
135
135
 
136
136
  export { type ApiFn, BrandStyle, type BrandStyleProps, type JoinConfig, JoinIsland, type JoinIslandProps, MembersArea, type MembersAreaProps, PaymentStep, type PaymentStepProps, type RescheduleProps, Rescheduler, type Slot, SlotPicker, type SlotPickerClasses, type SlotPickerProps, dayKey, dayLabel, fmtDate, fmtMoney, fullLabel, groupSlotsByDay, timeLabel, tzShort };
@@ -12,10 +12,10 @@ import {
12
12
  groupSlotsByDay,
13
13
  timeLabel,
14
14
  tzShort
15
- } from "../../chunk-ILGUJXSB.js";
15
+ } from "../../chunk-4COPR4SY.js";
16
16
  import {
17
17
  BrandStyle
18
- } from "../../chunk-3JG5X2LT.js";
18
+ } from "../../chunk-3QKGKUTX.js";
19
19
  export {
20
20
  BrandStyle,
21
21
  JoinIsland,
@@ -51,8 +51,22 @@ interface ChapterBrandTokens {
51
51
  danger?: string;
52
52
  chart1?: string;
53
53
  chart2?: string;
54
+ chart3?: string;
55
+ chart4?: string;
56
+ chart5?: string;
57
+ chart6?: string;
54
58
  chartPositive?: string;
55
59
  chartNegative?: string;
60
+ /** Admin/application content width, e.g. `"960px"`. */
61
+ contentWidth?: string;
62
+ panelRadius?: string;
63
+ panelShadow?: string;
64
+ masterDetailColumns?: string;
65
+ masterDetailMinHeight?: string;
66
+ /** Chapter-admin page padding shorthand. */
67
+ pagePadding?: string;
68
+ /** Chapter-admin workspace-tab padding shorthand. */
69
+ workspacePadding?: string;
56
70
  }
57
71
  /** Brand tokens that theme the site: theme, semantic roles, fonts, and logos. */
58
72
  interface ChapterBrand {
@@ -51,8 +51,22 @@ interface ChapterBrandTokens {
51
51
  danger?: string;
52
52
  chart1?: string;
53
53
  chart2?: string;
54
+ chart3?: string;
55
+ chart4?: string;
56
+ chart5?: string;
57
+ chart6?: string;
54
58
  chartPositive?: string;
55
59
  chartNegative?: string;
60
+ /** Admin/application content width, e.g. `"960px"`. */
61
+ contentWidth?: string;
62
+ panelRadius?: string;
63
+ panelShadow?: string;
64
+ masterDetailColumns?: string;
65
+ masterDetailMinHeight?: string;
66
+ /** Chapter-admin page padding shorthand. */
67
+ pagePadding?: string;
68
+ /** Chapter-admin workspace-tab padding shorthand. */
69
+ workspacePadding?: string;
56
70
  }
57
71
  /** Brand tokens that theme the site: theme, semantic roles, fonts, and logos. */
58
72
  interface ChapterBrand {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@odla-ai/chapter",
3
- "version": "0.21.0",
3
+ "version": "0.22.1",
4
4
  "description": "A leader/follower foundation for branded membership sites: shared CRM, admin, auth, payments, booking, and explicit record delivery from one defineChapter config.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://odla.ai/docs/packages/chapter",
@@ -70,16 +70,20 @@
70
70
  "prepublishOnly": "npm run build && npm test"
71
71
  },
72
72
  "dependencies": {
73
- "@odla-ai/crm": "^0.2.0",
73
+ "@odla-ai/crm": "^0.3.1",
74
74
  "jose": "^6.2.3"
75
75
  },
76
76
  "peerDependencies": {
77
+ "@odla-ai/auth-clerk": ">=0.4.1 <1.0.0",
77
78
  "@odla-ai/calendar": ">=0.2.0 <1.0.0",
78
- "@odla-ai/db": ">=0.6.0 <1.0.0",
79
- "@odla-ai/ui": ">=0.11.0 <1.0.0",
80
- "react": ">=18"
79
+ "@odla-ai/db": ">=0.6.7 <1.0.0",
80
+ "@odla-ai/ui": ">=0.12.1 <1.0.0",
81
+ "preact": ">=10.29.7 <11"
81
82
  },
82
83
  "peerDependenciesMeta": {
84
+ "@odla-ai/auth-clerk": {
85
+ "optional": true
86
+ },
83
87
  "@odla-ai/calendar": {
84
88
  "optional": true
85
89
  },
@@ -88,9 +92,6 @@
88
92
  },
89
93
  "@odla-ai/ui": {
90
94
  "optional": true
91
- },
92
- "react": {
93
- "optional": true
94
95
  }
95
96
  },
96
97
  "devDependencies": {
@@ -99,10 +100,9 @@
99
100
  "@odla-ai/db": "*",
100
101
  "@odla-ai/ui": "*",
101
102
  "@types/node": "^26.1.0",
102
- "@types/react": "^19.2.7",
103
103
  "@vitest/coverage-v8": "^4.1.9",
104
- "react": "^19.2.7",
105
- "react-dom": "^19.2.7",
104
+ "preact": "10.29.7",
105
+ "preact-render-to-string": "6.7.0",
106
106
  "tsup": "^8.3.0",
107
107
  "typescript": "^6.0.3",
108
108
  "vitest": "^4.1.9"
@@ -118,7 +118,7 @@ npx @odla-ai/cli@0.17.1 capabilities --json
118
118
 
119
119
  Pin the exact known-good package matrix from the installed Chapter README.
120
120
  For this runbook, first require
121
- `npm view @odla-ai/chapter@0.21.0 version` to succeed. Install every runtime,
121
+ `npm view @odla-ai/chapter@0.22.0 version` to succeed. Install every runtime,
122
122
  toolchain, CLI, and security package with the exact versions in that README;
123
123
  never use `latest`, a range, or `--legacy-peer-deps`.
124
124
 
@@ -356,9 +356,18 @@ fixture. Compare markup/behavior and the Phase 0 screenshots before deletion.
356
356
  - Replace join orchestration with `JoinIsland`, member behavior with
357
357
  `MembersArea`, and admin with `<ChapterAdmin chapter={chapter}/>` only after
358
358
  field/auth/action parity.
359
- - Preserve the site's global header with `chrome="none"` or `renderHeader`.
360
- Chapter mode defaults to Dashboard, People, and Settings; move former global
361
- Billing, Calendar, Email, and per-record operations into nested tabs.
359
+ - Preserve the site's global header with the default `chrome="embedded"`;
360
+ use `chrome="standalone"` only when Chapter should supply the masthead, or
361
+ `renderHeader` when the header belongs inside the Chapter scope. Chapter mode
362
+ defaults to Dashboard, People, and Settings; move former global Billing,
363
+ Calendar, Email, and per-record operations into nested tabs.
364
+ - Preserve a distinctive People summary, rail, and record heading with
365
+ `collectionSection`'s `renderSummary`, `renderMaster`, and
366
+ `renderDetailHeader` slots. Replace specialized record tabs rather than
367
+ flattening them into global navigation.
368
+ - Application-backed pipeline changes must use a lifecycle adapter that invokes
369
+ the authoritative application endpoints. Prove approve/refund/manual
370
+ transition side effects; a raw CRM `setStage` is not parity.
362
371
  - Accept old query/path admin links as inbound compatibility URLs, but generate
363
372
  canonical fragment links such as
364
373
  `/admin/#people/person/record-id/profile`. Prove refresh, back/forward, and
@@ -18,12 +18,12 @@ Chapter supplies:
18
18
  - the validated `defineChapter()` engine and generated schema/rules/seed;
19
19
  - the Cloudflare Worker API through `chapterWorker()`;
20
20
  - the join, payment, booking, member, admin, CRM, and network behavior;
21
- - member-facing React components and the complete Clerk-gated admin console;
21
+ - member-facing Preact components and the complete Clerk-gated admin console;
22
22
  - the provisioning descriptor consumed by `@odla-ai/cli`.
23
23
 
24
24
  Chapter does **not** generate a website. The host still owns:
25
25
 
26
- - `package.json`, Vite/React or Vite/Preact, HTML entries, routing, and CSS;
26
+ - `package.json`, Vite/Preact, HTML entries, routing, and CSS;
27
27
  - public pages, navigation, copy, imagery, SEO, legal text, and the join form;
28
28
  - the member auth wrapper that gives `MembersArea` an authenticated `api`;
29
29
  - Wrangler configuration, observability wrapping, provider setup, and tests.
@@ -115,31 +115,30 @@ npm init -y
115
115
  npx @odla-ai/cli@0.17.1 setup
116
116
  ```
117
117
 
118
- Verify every release target exists, then install the exact known-good React
118
+ Verify every release target exists, then install the exact known-good Preact
119
119
  host matrix. Do not copy these commands into a pre-release branch where
120
- `@odla-ai/chapter@0.21.0` is not yet on npm.
120
+ `@odla-ai/chapter@0.22.1` is not yet on npm.
121
121
 
122
122
  ```sh
123
- npm view @odla-ai/chapter@0.21.0 version
123
+ npm view @odla-ai/chapter@0.22.1 version
124
124
  npm install --save-exact \
125
- @odla-ai/chapter@0.21.0 @odla-ai/ui@0.11.0 \
126
- @odla-ai/crm@0.2.0 @odla-ai/db@0.6.6 \
125
+ @odla-ai/chapter@0.22.1 @odla-ai/ui@0.12.1 \
126
+ @odla-ai/crm@0.3.1 @odla-ai/db@0.6.7 \
127
127
  @odla-ai/calendar@0.2.0 @odla-ai/email@0.3.1 \
128
- @odla-ai/auth-clerk@0.4.0 @odla-ai/o11y@2.2.2 \
129
- jose@6.2.3 react@19.2.7 react-dom@19.2.7
128
+ @odla-ai/auth-clerk@0.4.1 @odla-ai/o11y@2.2.2 \
129
+ jose@6.2.3 preact@10.29.7
130
130
  npm install --save-dev --save-exact \
131
131
  @odla-ai/cli@0.17.1 @odla-ai/security@0.3.1 \
132
132
  @cloudflare/workers-types@4.20260702.1 \
133
- @types/react@19.2.17 @types/react-dom@19.2.3 \
134
- @vitejs/plugin-react@6.0.3 typescript@6.0.3 \
133
+ typescript@6.0.3 \
135
134
  vite@8.1.4 vitest@4.1.10 wrangler@4.107.0
136
135
  npx odla-ai capabilities --json
137
136
  ```
138
137
 
139
138
  The parent `README.md` repeats this version matrix. This runbook standardizes on
140
- React; a Preact host needs its own exact, tested compatibility matrix. Install
141
- auth-clerk explicitly: it is deliberately not a Chapter peer because
142
- worker-only and member-only consumers do not need it.
139
+ Preact. Install auth-clerk explicitly when adopting the admin entry; it is an
140
+ optional Chapter peer because worker-only and member-only consumers do not need
141
+ it.
143
142
 
144
143
  Required host scripts:
145
144
 
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/brand.ts","../src/ui/brand-style.tsx"],"sourcesContent":["// 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 chartPositive: \"--ui-chart-pos\",\n chartNegative: \"--ui-chart-neg\",\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 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 return css;\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":";AAaA,SAAS,WAAW,KAAqB;AACvC,SAAO,IAAI,WAAW,IAAI,IAAI,MAAM,KAAK,GAAG;AAC9C;AAKA,SAAS,WAAW,OAAuB;AACzC,SAAO,MAAM,QAAQ,YAAY,EAAE,EAAE,KAAK;AAC5C;AAEA,SAAS,aAAa,SAAuD;AAC3E,QAAM,QAAkB,CAAC;AACzB,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,WAAW,CAAC,CAAC,GAAG;AACxD,QAAI,OAAO,UAAU,YAAY,MAAM,KAAK,EAAG,OAAM,KAAK,GAAG,WAAW,GAAG,CAAC,KAAK,WAAW,KAAK,CAAC,GAAG;AAAA,EACvG;AACA,SAAO;AACT;AAEA,IAAM,aAAuD;AAAA,EAC3D,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,UAAU;AAAA,EACV,MAAM;AAAA,EACN,WAAW;AAAA,EACX,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,cAAc;AAAA,EACd,QAAQ;AAAA,EACR,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,MAAM;AAAA,EACN,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,eAAe;AAAA,EACf,eAAe;AACjB;AAEA,SAAS,cAAc,QAAkD;AACvE,SAAO,OAAO,QAAQ,UAAU,CAAC,CAAC,EAAE;AAAA,IAAQ,CAAC,CAAC,KAAK,KAAK,MACtD,OAAO,UAAU,YAAY,MAAM,KAAK,IACpC,CAAC,GAAG,WAAW,GAA+B,CAAC,KAAK,WAAW,KAAK,CAAC,GAAG,IACxE,CAAC;AAAA,EACP;AACF;AAYO,SAAS,YACd,OACA,UAAiC,CAAC,GAC1B;AACR,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,QAAQ,CAAC,GAAG,aAAa,MAAM,OAAO,GAAG,GAAG,cAAc,MAAM,MAAM,CAAC;AAC7E,QAAM,QAAQ,MAAM;AACpB,MAAI,OAAO,QAAS,OAAM,KAAK,sBAAsB,WAAW,MAAM,OAAO,CAAC,GAAG;AACjF,MAAI,OAAO,KAAM,OAAM,KAAK,mBAAmB,WAAW,MAAM,IAAI,CAAC,GAAG;AACxE,MAAI,OAAO,QAAS,OAAM,KAAK,sBAAsB,WAAW,MAAM,OAAO,CAAC,GAAG;AACjF,QAAM,OAAO,CAAC,GAAG,aAAa,MAAM,WAAW,GAAG,GAAG,cAAc,MAAM,UAAU,CAAC;AACpF,QAAM,WAAW,QAAQ,YAAY;AACrC,QAAM,eAAe,aAAa,UAAU,6BAA6B,GAAG,QAAQ;AACpF,QAAM,iBAAiB,aAAa,UAChC,oCACA,GAAG,QAAQ;AAEf,MAAI,MAAM,MAAM,SAAS,GAAG,QAAQ;AAAA,IAAS,MAAM,KAAK,MAAM,CAAC;AAAA;AAAA,IAAU;AACzE,MAAI,KAAK,QAAQ;AACf,UAAM,QAAQ;AAAA,IAAQ,KAAK,KAAK,MAAM,CAAC;AAAA;AACvC,WAAO,GAAG,YAAY,IAAI,KAAK;AAAA;AAAA,IAA8C,cAAc,IAAI,KAAK;AAAA;AAAA;AAAA,EACtG;AACA,SAAO;AACT;;;AC7ES;AAHF,SAAS,WAAW,OAAwB;AACjD,QAAM,MAAM,YAAY,MAAM,OAAO,EAAE,UAAU,MAAM,SAAS,CAAC;AACjE,MAAI,CAAC,IAAK,QAAO;AACjB,SAAO,oBAAC,WAAO,eAAI;AACrB;","names":[]}