@odla-ai/chapter 0.21.0 → 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.
@@ -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,8 +1,37 @@
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-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;
@@ -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;
81
+ /** Canonical nested view used when a workspace is opened without one. */
82
+ defaultViewId?: string;
51
83
  render: (ctx: AdminSectionContext) => ReactNode;
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) => ReactNode;
115
+ renderAccountMenu?: (props: AdminAccountMenuProps) => ReactNode;
116
+ renderWorkspaceNav?: (props: AdminWorkspaceNavProps) => ReactNode;
72
117
  }
73
118
 
74
119
  /** Configuration for the Clerk-gated, brand-scoped Chapter admin console. */
@@ -84,9 +129,13 @@ 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) => ReactNode;
136
+ renderWorkspaceNav?: (props: AdminWorkspaceNavProps) => ReactNode;
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
141
  declare function ChapterAdmin(props: ChapterAdminProps): react.JSX.Element;
@@ -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) => 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;
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
 
@@ -223,4 +289,4 @@ declare function AdminNote({ children }: {
223
289
  children: ReactNode;
224
290
  }): react.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-IWW5VAAC.js";
28
+ import "../../chunk-VZPBTTO3.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
3
  import 'react';
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
@@ -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-IWW5VAAC.js";
40
43
  import {
41
44
  BrandStyle
42
- } from "../chunk-3JG5X2LT.js";
45
+ } from "../chunk-VZPBTTO3.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
1
  import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
- import { a as ChapterBrand } from '../../types-CXDiDj_s.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. */
@@ -15,7 +15,7 @@ import {
15
15
  } from "../../chunk-ILGUJXSB.js";
16
16
  import {
17
17
  BrandStyle
18
- } from "../../chunk-3JG5X2LT.js";
18
+ } from "../../chunk-VZPBTTO3.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.0",
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,13 +70,13 @@
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.0",
74
74
  "jose": "^6.2.3"
75
75
  },
76
76
  "peerDependencies": {
77
77
  "@odla-ai/calendar": ">=0.2.0 <1.0.0",
78
78
  "@odla-ai/db": ">=0.6.0 <1.0.0",
79
- "@odla-ai/ui": ">=0.11.0 <1.0.0",
79
+ "@odla-ai/ui": ">=0.12.0 <1.0.0",
80
80
  "react": ">=18"
81
81
  },
82
82
  "peerDependenciesMeta": {
@@ -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
@@ -117,13 +117,13 @@ npx @odla-ai/cli@0.17.1 setup
117
117
 
118
118
  Verify every release target exists, then install the exact known-good React
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.0` 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.0 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.0 @odla-ai/ui@0.12.0 \
126
+ @odla-ai/crm@0.3.0 @odla-ai/db@0.6.6 \
127
127
  @odla-ai/calendar@0.2.0 @odla-ai/email@0.3.1 \
128
128
  @odla-ai/auth-clerk@0.4.0 @odla-ai/o11y@2.2.2 \
129
129
  jose@6.2.3 react@19.2.7 react-dom@19.2.7
@@ -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":[]}