@odla-ai/chapter 0.20.2 → 0.22.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.
@@ -31,8 +31,50 @@ interface Rule {
31
31
  }
32
32
  /** Namespace → rule set. */
33
33
  type DbRules = Record<string, Rule>;
34
- /** Brand tokens that theme the site: palette, fonts, wordmark, nav, logos. */
34
+ /** Typed semantic roles shared by the public site and application surfaces. */
35
+ interface ChapterBrandTokens {
36
+ background?: string;
37
+ surface?: string;
38
+ surface2?: string;
39
+ text?: string;
40
+ textMuted?: string;
41
+ textFaint?: string;
42
+ border?: string;
43
+ borderStrong?: string;
44
+ accent?: string;
45
+ accentStrong?: string;
46
+ accentSoft?: string;
47
+ onAccent?: string;
48
+ good?: string;
49
+ warn?: string;
50
+ danger?: string;
51
+ chart1?: string;
52
+ chart2?: string;
53
+ chart3?: string;
54
+ chart4?: string;
55
+ chart5?: string;
56
+ chart6?: string;
57
+ chartPositive?: string;
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;
69
+ }
70
+ /** Brand tokens that theme the site: theme, semantic roles, fonts, and logos. */
35
71
  interface ChapterBrand {
72
+ /** Preloaded @odla-ai/ui theme name used by ThemeScope. */
73
+ theme?: string;
74
+ /** Admin/application color mode. Explicit light is the safe default. */
75
+ colorScheme?: "light" | "dark" | "system";
76
+ tokens?: ChapterBrandTokens;
77
+ tokensDark?: ChapterBrandTokens;
36
78
  /** Palette overrides written into styles.css :root (e.g. `{ moss: "#2F3E34" }`
37
79
  * or `--ui-*` token names). Applies in BOTH light and dark unless overridden
38
80
  * by {@link paletteDark}. */
@@ -66,7 +108,7 @@ interface ChapterNetworkTarget {
66
108
  id: string;
67
109
  /** Human-facing site name. Defaults to `id`. */
68
110
  name?: string;
69
- /** Absolute follower origin, e.g. `https://silver.example.com`. */
111
+ /** Absolute follower origin, e.g. `https://chapter.example.com`. */
70
112
  url: string;
71
113
  /** Leader-vault key holding this follower's share secret. Defaults to
72
114
  * `network_share_<id-with-underscores>`. */
@@ -137,10 +179,10 @@ interface ChapterScheduling {
137
179
  }
138
180
  /** How a signed-in user's role is determined.
139
181
  *
140
- * - `"claim"` reads a role ladder from a JWT claim (Silver & Salt's model:
182
+ * - `"claim"` reads a role ladder from a JWT claim (the member-site model:
141
183
  * `provisional → member → admin`). Admin is the last (highest) rung; the
142
184
  * read-only `superAdmins` tier sits above it.
143
- * - `"table"` gates on the deny-all `admins` allowlist (Built Not Found's model):
185
+ * - `"table"` gates on the deny-all `admins` allowlist (the hub model):
144
186
  * a binary "email is an admin or isn't", set only in odla Studio.
145
187
  *
146
188
  * Defaults: `"claim"` in `chapter` mode, `"table"` in `hub` mode. */
@@ -167,7 +209,7 @@ interface ResolvedAuth {
167
209
  }
168
210
  /** The application status pipeline. Which statuses exist, and the subsets a site
169
211
  * allows a call to be booked from / an application approved from. Defaults to
170
- * Silver & Salt's pipeline. Status never moves backwards (package-enforced). */
212
+ * the reference pipeline. Status never moves backwards (package-enforced). */
171
213
  interface ChapterPipeline {
172
214
  stages?: readonly string[];
173
215
  bookableFrom?: readonly string[];
@@ -184,8 +226,8 @@ interface ResolvedPipeline {
184
226
  }
185
227
  /** The application (join form) validation surface — which string fields are
186
228
  * required vs accepted, their max lengths, and the request body cap. Drives
187
- * submit validation + the CRM slot projection; defaults to Silver & Salt's form.
188
- * The `applications` schema attrs stay fixed (byte-equal to S&S); this is
229
+ * submit validation + the CRM slot projection; defaults to the reference form.
230
+ * The `applications` schema attrs stay fixed; this is
189
231
  * validation config, not schema generation. */
190
232
  interface ChapterApplication {
191
233
  required?: readonly string[];
@@ -257,7 +299,7 @@ interface ChapterConfig {
257
299
  /** `notificationEmail` required in `chapter` mode. */
258
300
  emails?: ChapterEmails;
259
301
  scheduling?: ChapterScheduling;
260
- /** Application status pipeline (stages + bookable/approvable subsets). Defaults to S&S's. */
302
+ /** Application status pipeline (stages + bookable/approvable subsets). */
261
303
  pipeline?: ChapterPipeline;
262
304
  /** Join-form validation (required/optional fields, max lengths, body cap). */
263
305
  application?: ChapterApplication;
@@ -1,133 +1,201 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
3
  import { CrmClient, Crm, CrmRecord } from '@odla-ai/crm';
4
- import { C as Chapter } from '../../types-D2fGiNG8.js';
4
+ import { C as Chapter } from '../../types-CgWUQ2MH.js';
5
+ import { CrmLifecycleAdapter, CrmWorkspaceMasterContext, CrmWorkspaceRecordContext, RecordPanelTab } from '@odla-ai/crm/ui';
5
6
 
6
- /** What each admin section receives. `client` is a CrmClient bound to the
7
- * signed-in admin's bearer token; `navigate` switches sections. */
8
- interface AdminSectionContext {
9
- client: CrmClient;
10
- getToken: () => Promise<string | null>;
11
- /** Switch to another section by id (e.g. Overview "jump in" buttons). */
12
- navigate: (sectionId: string) => void;
7
+ /** Normalized public auth configuration consumed by the Clerk gate. */
8
+ interface ChapterAdminAuthConfig {
9
+ publishableKey: string | null;
13
10
  }
14
- /** One admin-console section: a nav label and a body renderer. */
15
- interface AdminSection {
16
- id: string;
17
- label: string;
18
- render: (ctx: AdminSectionContext) => ReactNode;
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;
19
30
  }
20
- /** How admin section state is reflected in the URL. Query routing is the
21
- * host-independent default: `/admin/?tab=people` reloads through the same static
22
- * file on SPA and multi-page sites alike. Path routing requires a host SPA
23
- * fallback for every `/admin/<section>` URL. */
24
- type AdminRouting = "query" | "path";
25
- /** Wordmark shown in the gate and top bar. `name` is the plain-text identity (and
26
- * the badge fallback); `wordmark` is an optional rich display node for brands
27
- * whose typography can't be a plain string (e.g. an ampersand wrapped to render
28
- * upright), and `badge` overrides the glyph. */
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>;
35
+
29
36
  type Brand = {
30
37
  name: string;
31
38
  badge?: string;
32
39
  wordmark?: ReactNode;
33
40
  };
34
- /** Resolve a section from the stable `?tab=` URL first, then a legacy path
35
- * segment for backwards-compatible deep links. */
41
+
42
+ /** Typed URL state for the admin's nested in-page workspaces. */
43
+ interface AdminRouteState {
44
+ workspaceId: string;
45
+ viewId?: string;
46
+ recordId?: string;
47
+ detailTab?: string;
48
+ }
49
+ /** URL encoding used for admin workspace state. */
50
+ type AdminRouting = "fragment" | "query" | "path";
51
+ type AdminRouteAliases = Readonly<Record<string, AdminRouteState>>;
52
+ /** Read canonical fragments first, then inbound query and legacy path links. */
53
+ declare function adminRouteFromUrl(url: URL, basePath: string, workspaceIds: readonly string[], aliases?: AdminRouteAliases): AdminRouteState;
54
+ /** Build a stable URL for one admin state. Fragment routing is the default. */
55
+ declare function adminRouteHref(current: URL, basePath: string, route: AdminRouteState, routing?: AdminRouting): string;
56
+ /** Backwards-compatible section helpers over the workspace router. */
36
57
  declare function adminSectionFromUrl(url: URL, basePath: string, sectionIds: readonly string[]): string;
37
- /** Build the next section URL without depending on the static host's 404/SPA
38
- * policy. Query mode collapses a legacy path route back to the admin mount. */
58
+ /** Build a compatibility URL for a top-level admin section. */
39
59
  declare function adminSectionHref(current: URL, basePath: string, id: string, routing?: AdminRouting): string;
40
60
 
41
- /** Props for {@link ChapterAdmin}. */
61
+ /** A workspace id or partial nested route passed to navigation helpers. */
62
+ type AdminRouteTarget = string | {
63
+ workspaceId?: string;
64
+ viewId?: string | null;
65
+ recordId?: string | null;
66
+ detailTab?: string | null;
67
+ };
68
+ /** Context shared by every admin workspace and its nested views. */
69
+ interface AdminSectionContext {
70
+ client: CrmClient;
71
+ getToken: () => Promise<string | null>;
72
+ currentUser?: Record<string, unknown>;
73
+ route: AdminRouteState;
74
+ navigate: (target: AdminRouteTarget) => void;
75
+ href: (target: AdminRouteTarget) => string;
76
+ }
77
+ /** One top-level admin workspace rendered inside the signed-in shell. */
78
+ interface AdminWorkspace {
79
+ id: string;
80
+ label: string;
81
+ /** Canonical nested view used when a workspace is opened without one. */
82
+ defaultViewId?: string;
83
+ render: (ctx: AdminSectionContext) => ReactNode;
84
+ }
85
+ /** Legacy name for a top-level workspace. @deprecated Use AdminWorkspace. */
86
+ type AdminSection = AdminWorkspace;
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
+ }
103
+ interface AdminShellProps {
104
+ workspaces: AdminWorkspace[];
105
+ basePath: string;
106
+ brand: Brand;
107
+ client: CrmClient;
108
+ getToken: () => Promise<string | null>;
109
+ signOut: () => void;
110
+ email: string | null;
111
+ currentUser?: Record<string, unknown>;
112
+ routing?: AdminRouting;
113
+ chrome?: AdminChrome;
114
+ renderHeader?: (props: AdminAccountMenuProps) => ReactNode;
115
+ renderAccountMenu?: (props: AdminAccountMenuProps) => ReactNode;
116
+ renderWorkspaceNav?: (props: AdminWorkspaceNavProps) => ReactNode;
117
+ }
118
+
119
+ /** Configuration for the Clerk-gated, brand-scoped Chapter admin console. */
42
120
  interface ChapterAdminProps {
43
- /** The resolved site config. When present it supplies the brand and, unless
44
- * `sections` is passed, the complete standard section catalog. */
45
121
  chapter?: Chapter;
46
- /** Explicit admin sections. Omit with `chapter` for the standard console. */
122
+ /** Replace or transform the standard workspace set. */
123
+ workspaces?: AdminWorkspace[] | ((defaults: readonly AdminWorkspace[]) => AdminWorkspace[]);
124
+ /** @deprecated Use workspaces. */
47
125
  sections?: AdminSection[];
48
- /** Admin mount path. The sign-in redirect + section routing derive from it,
49
- * so the "bounce to home after sign-in" bug is impossible. Default "/admin". */
50
126
  basePath?: string;
51
- /** Wordmark shown in the gate + top bar. */
52
127
  brand?: Brand;
53
- /** CRM route mount. Default "/api/crm". */
54
128
  crmBasePath?: string;
55
- /** Origin for /api/config and /api/me. Default same origin. */
56
129
  apiBase?: string;
57
- /** Section URL strategy. Default `"query"` (`/admin/?tab=people`), which
58
- * works with both SPA and ordinary static hosting. `"path"` requires the host
59
- * to serve the admin entry for every `/admin/<section>` URL. */
130
+ /** Default fragment: `/admin/#people/person/record-id/profile`. */
60
131
  routing?: AdminRouting;
132
+ /** Default embedded. Standalone adds packaged global chrome. */
133
+ chrome?: AdminChrome;
134
+ renderHeader?: AdminShellProps["renderHeader"];
135
+ renderAccountMenu?: (props: AdminAccountMenuProps) => ReactNode;
136
+ renderWorkspaceNav?: (props: AdminWorkspaceNavProps) => ReactNode;
137
+ /** Adapt existing config/current-user endpoints without rewriting auth. */
138
+ auth?: ChapterAdminAuthAdapter;
61
139
  }
62
- /**
63
- * The whole admin console for a chapter/hub: fetches the Clerk publishable key
64
- * (/api/config), gates on sign-in with the post-sign-in redirect derived from
65
- * `basePath`, checks the odla-db admins allowlist (/api/me), and renders a
66
- * TopBar shell over the caller's `sections`. The gate/shell/redirect are owned
67
- * here so no site re-wires (or re-bugs) them.
68
- */
140
+ /** Render the complete authenticated admin application from one Chapter config. */
69
141
  declare function ChapterAdmin(props: ChapterAdminProps): react.JSX.Element;
70
142
 
71
- /** Build the familiar admin surface from a chapter's configured CRM types.
72
- * Hubs receive every CRM collection. Membership chapters additionally receive
73
- * dashboard, meeting, availability, billing, and email operations. */
143
+ /** Default follower grammar: three workspaces with nested operational views. */
144
+ declare function defaultAdminWorkspaces(chapter: Chapter): AdminWorkspace[];
145
+
146
+ /** Legacy default catalog. @deprecated Use defaultAdminWorkspaces. */
74
147
  declare function defaultAdminSections(chapter: Chapter): AdminSection[];
75
148
 
76
- /** Options for {@link peopleSection}. */
149
+ /** Application-authoritative stage transitions used by Chapter collections. */
150
+ declare function applicationLifecycleAdapter(ctx: Pick<AdminSectionContext, "getToken">): CrmLifecycleAdapter;
151
+
152
+ /** Configuration for the person-collection compatibility preset. */
77
153
  interface PeopleSectionOptions {
78
- /** The chapter's resolved CRM engine — `chapter.crm` from defineChapter. */
79
154
  crm: Crm;
80
- /** Section id / route segment. Default "people". */
81
155
  id?: string;
82
- /** Nav label. Default "People". */
83
156
  label?: string;
84
- /** The record type to list. Default "person". */
85
157
  type?: string;
86
158
  }
87
- /** Options for {@link collectionSection}. */
159
+ /** Configuration for one CRM collection and its application-specific tabs. */
88
160
  interface CollectionSectionOptions {
89
- /** The chapter's resolved CRM engine — `chapter.crm` from defineChapter. */
90
161
  crm: Crm;
91
- /** The CRM record type to list (e.g. `"person"`, `"deal"`, `"investment"`). */
92
162
  type: string;
93
- /** Section id / route segment. Defaults to `type`. */
94
163
  id?: string;
95
- /** Nav label. Defaults to the type's plural label from the CRM config. */
96
164
  label?: string;
97
- /** Show the lifecycle action panel (role change / approve / refund) on an open
98
- * record — for application-backed people. Default `true` for `type: "person"`,
99
- * `false` otherwise (a `deal`/`investment` has no approve/refund). */
100
165
  lifecycle?: boolean;
101
- /** Role ladder for the role selector. Default `["provisional","member","admin"]`
102
- * — pass your `chapter.auth.ladder` if you use a custom ladder. */
103
166
  roles?: readonly string[];
167
+ networkSharing?: boolean;
168
+ lifecycleAdapter?: (CrmLifecycleAdapter | ((ctx: AdminSectionContext) => CrmLifecycleAdapter));
169
+ requireLifecycleAdapter?: boolean;
170
+ renderSummary?: (context: CrmWorkspaceMasterContext, ctx: AdminSectionContext) => ReactNode;
171
+ renderMaster?: (context: CrmWorkspaceMasterContext, ctx: AdminSectionContext) => ReactNode;
172
+ renderDetailHeader?: (context: CrmWorkspaceRecordContext, ctx: AdminSectionContext) => ReactNode;
173
+ renderEmptyDetail?: (context: CrmWorkspaceMasterContext, ctx: AdminSectionContext) => ReactNode;
174
+ hrefForRecord?: (record: CrmRecord, ctx: AdminSectionContext) => string;
175
+ extendRecordTabs?: (tabs: readonly RecordPanelTab[], context: CrmWorkspaceRecordContext) => readonly RecordPanelTab[];
104
176
  }
105
- /**
106
- * Build a CRM collection admin section for ANY declared record type: a
107
- * server-driven records table plus a facet-composed detail drawer (fields,
108
- * pipeline stage, tags) over the token-bound CrmClient, and — for people — a
109
- * lifecycle action panel (role change, approve, refund) wired to the
110
- * `/api/admin/*` routes. This is the generic kit — call it once per collection a
111
- * site runs (members, deals, investments), and each renders with the same UX.
112
- * CRM edits go through `/api/crm/*` and lifecycle actions through `/api/admin/*`,
113
- * never a direct db write, so the invariants hold.
114
- */
177
+ /** Build one CRM collection as an admin workspace. */
115
178
  declare function collectionSection(options: CollectionSectionOptions): AdminSection;
116
- /**
117
- * Build the People/CRM admin section — {@link collectionSection} preset to the
118
- * `person` type (so the lifecycle actions are on). New collections use
119
- * `collectionSection`.
120
- */
179
+ /** Person-collection preset. */
121
180
  declare function peopleSection(options: PeopleSectionOptions & {
122
181
  roles?: readonly string[];
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"];
190
+ extendRecordTabs?: CollectionSectionOptions["extendRecordTabs"];
123
191
  }): AdminSection;
124
192
 
125
- /** Options for {@link dashboardSection}. */
193
+ /** Labels and ids for a composed dashboard workspace entry. */
126
194
  interface DashboardSectionOptions {
127
195
  id?: string;
128
196
  label?: string;
129
197
  }
130
- /** Build the Dashboard admin section over `GET /api/admin/dashboard`. */
198
+ /** Build the operational dashboard workspace over `/api/admin/dashboard`. */
131
199
  declare function dashboardSection(options?: DashboardSectionOptions): AdminSection;
132
200
 
133
201
  /** Options for {@link billingSection}. */
@@ -214,12 +282,11 @@ declare function Panel({ title, actions, children }: {
214
282
  actions?: ReactNode;
215
283
  children: ReactNode;
216
284
  }): react.JSX.Element;
217
- /** A loud banner when the odla-ui theme token layer is missing — the silent-and-
218
- * total failure S&S flagged. Renders nothing when the theme is present. */
285
+ /** A loud banner when the odla-ui theme token layer is missing. */
219
286
  declare function ThemeWarning(): react.JSX.Element | null;
220
287
  /** A centered status line for loading / empty / error states. */
221
288
  declare function AdminNote({ children }: {
222
289
  children: ReactNode;
223
290
  }): react.JSX.Element;
224
291
 
225
- export { AdminNote, AdminPage, type AdminResource, type AdminRouting, type AdminSection, type AdminSectionContext, 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, adminSectionFromUrl, adminSectionHref, availabilitySection, billingSection, collectionSection, dashboardSection, defaultAdminSections, 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 };
@@ -7,19 +7,25 @@ import {
7
7
  RecordActions,
8
8
  ThemeWarning,
9
9
  adminFetch,
10
+ adminRouteFromUrl,
11
+ adminRouteHref,
10
12
  adminSectionFromUrl,
11
13
  adminSectionHref,
14
+ applicationLifecycleAdapter,
12
15
  availabilitySection,
13
16
  billingSection,
14
17
  collectionSection,
15
18
  dashboardSection,
16
19
  defaultAdminSections,
20
+ defaultAdminWorkspaces,
17
21
  emailSection,
22
+ loadChapterAdminConfig,
23
+ loadChapterAdminUser,
18
24
  meetingsSection,
19
25
  peopleSection,
20
26
  useAdminResource
21
- } from "../../chunk-UIWFEESI.js";
22
- import "../../chunk-MKXHZMAP.js";
27
+ } from "../../chunk-IWW5VAAC.js";
28
+ import "../../chunk-VZPBTTO3.js";
23
29
  export {
24
30
  AdminNote,
25
31
  AdminPage,
@@ -29,14 +35,20 @@ export {
29
35
  RecordActions,
30
36
  ThemeWarning,
31
37
  adminFetch,
38
+ adminRouteFromUrl,
39
+ adminRouteHref,
32
40
  adminSectionFromUrl,
33
41
  adminSectionHref,
42
+ applicationLifecycleAdapter,
34
43
  availabilitySection,
35
44
  billingSection,
36
45
  collectionSection,
37
46
  dashboardSection,
38
47
  defaultAdminSections,
48
+ defaultAdminWorkspaces,
39
49
  emailSection,
50
+ loadChapterAdminConfig,
51
+ loadChapterAdminUser,
40
52
  meetingsSection,
41
53
  peopleSection,
42
54
  useAdminResource
@@ -1,5 +1,6 @@
1
- export { AdminNote, AdminPage, AdminResource, AdminRouting, AdminSection, AdminSectionContext, AvailabilitySectionOptions, BillingSectionOptions, ChapterAdmin, ChapterAdminProps, CollectionSectionOptions, DashboardSectionOptions, EmailSectionOptions, MeetingsSectionOptions, NetworkShareActions, NetworkShareActionsProps, Panel, PeopleSectionOptions, RecordActions, RecordActionsProps, ThemeWarning, adminFetch, adminSectionFromUrl, adminSectionHref, availabilitySection, billingSection, collectionSection, dashboardSection, defaultAdminSections, 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
3
  import 'react';
4
4
  import '@odla-ai/crm';
5
- import '../types-D2fGiNG8.js';
5
+ import '../types-CgWUQ2MH.js';
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-LME2IHY5.js";
15
+ } from "../chunk-ILGUJXSB.js";
16
16
  import {
17
17
  AdminNote,
18
18
  AdminPage,
@@ -22,21 +22,27 @@ import {
22
22
  RecordActions,
23
23
  ThemeWarning,
24
24
  adminFetch,
25
+ adminRouteFromUrl,
26
+ adminRouteHref,
25
27
  adminSectionFromUrl,
26
28
  adminSectionHref,
29
+ applicationLifecycleAdapter,
27
30
  availabilitySection,
28
31
  billingSection,
29
32
  collectionSection,
30
33
  dashboardSection,
31
34
  defaultAdminSections,
35
+ defaultAdminWorkspaces,
32
36
  emailSection,
37
+ loadChapterAdminConfig,
38
+ loadChapterAdminUser,
33
39
  meetingsSection,
34
40
  peopleSection,
35
41
  useAdminResource
36
- } from "../chunk-UIWFEESI.js";
42
+ } from "../chunk-IWW5VAAC.js";
37
43
  import {
38
44
  BrandStyle
39
- } from "../chunk-MKXHZMAP.js";
45
+ } from "../chunk-VZPBTTO3.js";
40
46
  export {
41
47
  AdminNote,
42
48
  AdminPage,
@@ -52,8 +58,11 @@ export {
52
58
  SlotPicker,
53
59
  ThemeWarning,
54
60
  adminFetch,
61
+ adminRouteFromUrl,
62
+ adminRouteHref,
55
63
  adminSectionFromUrl,
56
64
  adminSectionHref,
65
+ applicationLifecycleAdapter,
57
66
  availabilitySection,
58
67
  billingSection,
59
68
  collectionSection,
@@ -61,11 +70,14 @@ export {
61
70
  dayKey,
62
71
  dayLabel,
63
72
  defaultAdminSections,
73
+ defaultAdminWorkspaces,
64
74
  emailSection,
65
75
  fmtDate,
66
76
  fmtMoney,
67
77
  fullLabel,
68
78
  groupSlotsByDay,
79
+ loadChapterAdminConfig,
80
+ loadChapterAdminUser,
69
81
  meetingsSection,
70
82
  peopleSection,
71
83
  timeLabel,
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
- import { a as ChapterBrand } from '../../types-D2fGiNG8.js';
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. */
@@ -126,6 +126,8 @@ declare function PaymentStep(props: PaymentStepProps): react.JSX.Element;
126
126
  /** Props for {@link BrandStyle}. */
127
127
  interface BrandStyleProps {
128
128
  brand: ChapterBrand | undefined;
129
+ /** CSS selector receiving the variables. Default `:root`. */
130
+ selector?: string;
129
131
  }
130
132
  /** Render a chapter's brand tokens as an inline <style> block (or nothing when
131
133
  * there is no brand to theme). */
@@ -12,10 +12,10 @@ import {
12
12
  groupSlotsByDay,
13
13
  timeLabel,
14
14
  tzShort
15
- } from "../../chunk-LME2IHY5.js";
15
+ } from "../../chunk-ILGUJXSB.js";
16
16
  import {
17
17
  BrandStyle
18
- } from "../../chunk-MKXHZMAP.js";
18
+ } from "../../chunk-VZPBTTO3.js";
19
19
  export {
20
20
  BrandStyle,
21
21
  JoinIsland,
@@ -1165,7 +1165,7 @@ function createStripeProvider(config) {
1165
1165
  "expand[0]": "latest_invoice.confirmation_secret",
1166
1166
  metadata: meta
1167
1167
  },
1168
- // The hardening S&S lacked: one subscription per application, so a client
1168
+ // Hardening: one subscription per application, so a client
1169
1169
  // retry between create and the db write can't orphan a second one.
1170
1170
  `sub:${input.applicationId}`
1171
1171
  );
@@ -1736,18 +1736,30 @@ var handleAdminPeopleRole = async (req, url, env, ctx) => {
1736
1736
  };
1737
1737
 
1738
1738
  // src/series.ts
1739
- function bucketSeries(points, now, weeks = 12) {
1740
- const WEEK = 7 * 864e5;
1741
- const end = now;
1742
- const start = end - weeks * WEEK;
1743
- const buckets = Array.from({ length: weeks }, (_, i) => ({ weekStart: start + i * WEEK, value: 0 }));
1744
- for (const p of points) {
1745
- if (!Number.isFinite(p.t) || p.t < start || p.t > end) continue;
1746
- const idx = Math.min(weeks - 1, Math.floor((p.t - start) / WEEK));
1747
- const bucket = buckets[idx];
1748
- if (bucket) bucket.value += Number.isFinite(p.v) ? p.v : 0;
1749
- }
1750
- return buckets;
1739
+ function periodSeries(points, now, count, periodMs) {
1740
+ const start = now - count * periodMs;
1741
+ const previousStart = start - count * periodMs;
1742
+ const current = Array.from({ length: count }, () => 0);
1743
+ const previous = Array.from({ length: count }, () => 0);
1744
+ for (const point of points) {
1745
+ if (!Number.isFinite(point.t) || !Number.isFinite(point.v)) continue;
1746
+ if (point.t >= start && point.t <= now) {
1747
+ const index = Math.min(count - 1, Math.floor((point.t - start) / periodMs));
1748
+ current[index] = (current[index] ?? 0) + point.v;
1749
+ } else if (point.t >= previousStart && point.t < start) {
1750
+ const index = Math.min(count - 1, Math.floor((point.t - previousStart) / periodMs));
1751
+ previous[index] = (previous[index] ?? 0) + point.v;
1752
+ }
1753
+ }
1754
+ const labels = current.map((_, index) => new Date(start + index * periodMs).toISOString().slice(5, 10));
1755
+ return { labels, current, previous };
1756
+ }
1757
+ function dashboardMetricData(points, now) {
1758
+ const DAY = 864e5;
1759
+ return {
1760
+ wow: periodSeries(points, now, 7, DAY),
1761
+ mom: periodSeries(points, now, 4, 7 * DAY)
1762
+ };
1751
1763
  }
1752
1764
  function subAnnualCents(sub) {
1753
1765
  const items = sub.items?.data ?? [];
@@ -1830,8 +1842,8 @@ var handleAdminDashboard = async (req, url, env, ctx) => {
1830
1842
  if (subsRes.ok) {
1831
1843
  const subs = rows(subsRes.body.data);
1832
1844
  const subMs = (s) => (s.created ?? 0) * 1e3;
1833
- membersSeries = bucketSeries(subs.map((s) => ({ t: subMs(s), v: 1 })), now);
1834
- revenueSeries = bucketSeries(subs.map((s) => ({ t: subMs(s), v: subAnnualCents(s) })), now);
1845
+ membersSeries = dashboardMetricData(subs.map((s) => ({ t: subMs(s), v: 1 })), now);
1846
+ revenueSeries = dashboardMetricData(subs.map((s) => ({ t: subMs(s), v: subAnnualCents(s) })), now);
1835
1847
  const active = subs.filter((s) => s.status === "active");
1836
1848
  revenue = {
1837
1849
  billingReady: true,
@@ -1843,7 +1855,7 @@ var handleAdminDashboard = async (req, url, env, ctx) => {
1843
1855
  };
1844
1856
  }
1845
1857
  }
1846
- const applicationsSeries = bucketSeries(apps.map((a) => ({ t: a.createdAt || 0, v: 1 })), now);
1858
+ const applicationsSeries = dashboardMetricData(apps.map((a) => ({ t: a.createdAt || 0, v: 1 })), now);
1847
1859
  return json({ applications, applicationsSeries, pipeline, pipelineDelta, calls, agenda, timezone, revenue, revenueSeries, membersSeries });
1848
1860
  };
1849
1861
  var handleAdminBilling = async (req, url, env, ctx) => {