@odla-ai/chapter 0.20.2 → 0.21.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,36 @@ 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
+ chartPositive?: string;
54
+ chartNegative?: string;
55
+ }
56
+ /** Brand tokens that theme the site: theme, semantic roles, fonts, and logos. */
35
57
  interface ChapterBrand {
58
+ /** Preloaded @odla-ai/ui theme name used by ThemeScope. */
59
+ theme?: string;
60
+ /** Admin/application color mode. Explicit light is the safe default. */
61
+ colorScheme?: "light" | "dark" | "system";
62
+ tokens?: ChapterBrandTokens;
63
+ tokensDark?: ChapterBrandTokens;
36
64
  /** Palette overrides written into styles.css :root (e.g. `{ moss: "#2F3E34" }`
37
65
  * or `--ui-*` token names). Applies in BOTH light and dark unless overridden
38
66
  * by {@link paletteDark}. */
@@ -66,7 +94,7 @@ interface ChapterNetworkTarget {
66
94
  id: string;
67
95
  /** Human-facing site name. Defaults to `id`. */
68
96
  name?: string;
69
- /** Absolute follower origin, e.g. `https://silver.example.com`. */
97
+ /** Absolute follower origin, e.g. `https://chapter.example.com`. */
70
98
  url: string;
71
99
  /** Leader-vault key holding this follower's share secret. Defaults to
72
100
  * `network_share_<id-with-underscores>`. */
@@ -137,10 +165,10 @@ interface ChapterScheduling {
137
165
  }
138
166
  /** How a signed-in user's role is determined.
139
167
  *
140
- * - `"claim"` reads a role ladder from a JWT claim (Silver & Salt's model:
168
+ * - `"claim"` reads a role ladder from a JWT claim (the member-site model:
141
169
  * `provisional → member → admin`). Admin is the last (highest) rung; the
142
170
  * read-only `superAdmins` tier sits above it.
143
- * - `"table"` gates on the deny-all `admins` allowlist (Built Not Found's model):
171
+ * - `"table"` gates on the deny-all `admins` allowlist (the hub model):
144
172
  * a binary "email is an admin or isn't", set only in odla Studio.
145
173
  *
146
174
  * Defaults: `"claim"` in `chapter` mode, `"table"` in `hub` mode. */
@@ -167,7 +195,7 @@ interface ResolvedAuth {
167
195
  }
168
196
  /** The application status pipeline. Which statuses exist, and the subsets a site
169
197
  * allows a call to be booked from / an application approved from. Defaults to
170
- * Silver & Salt's pipeline. Status never moves backwards (package-enforced). */
198
+ * the reference pipeline. Status never moves backwards (package-enforced). */
171
199
  interface ChapterPipeline {
172
200
  stages?: readonly string[];
173
201
  bookableFrom?: readonly string[];
@@ -184,8 +212,8 @@ interface ResolvedPipeline {
184
212
  }
185
213
  /** The application (join form) validation surface — which string fields are
186
214
  * 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
215
+ * submit validation + the CRM slot projection; defaults to the reference form.
216
+ * The `applications` schema attrs stay fixed; this is
189
217
  * validation config, not schema generation. */
190
218
  interface ChapterApplication {
191
219
  required?: readonly string[];
@@ -257,7 +285,7 @@ interface ChapterConfig {
257
285
  /** `notificationEmail` required in `chapter` mode. */
258
286
  emails?: ChapterEmails;
259
287
  scheduling?: ChapterScheduling;
260
- /** Application status pipeline (stages + bookable/approvable subsets). Defaults to S&S's. */
288
+ /** Application status pipeline (stages + bookable/approvable subsets). */
261
289
  pipeline?: ChapterPipeline;
262
290
  /** Join-form validation (required/optional fields, max lengths, body cap). */
263
291
  application?: ChapterApplication;
@@ -1,133 +1,135 @@
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-CXDiDj_s.js';
5
+ import { RecordPanelTab, CrmWorkspaceRecordContext } 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. */
7
+ type Brand = {
8
+ name: string;
9
+ badge?: string;
10
+ wordmark?: ReactNode;
11
+ };
12
+
13
+ /** Typed URL state for the admin's nested in-page workspaces. */
14
+ interface AdminRouteState {
15
+ workspaceId: string;
16
+ viewId?: string;
17
+ recordId?: string;
18
+ detailTab?: string;
19
+ }
20
+ /** URL encoding used for admin workspace state. */
21
+ type AdminRouting = "fragment" | "query" | "path";
22
+ type AdminRouteAliases = Readonly<Record<string, AdminRouteState>>;
23
+ /** Read canonical fragments first, then inbound query and legacy path links. */
24
+ declare function adminRouteFromUrl(url: URL, basePath: string, workspaceIds: readonly string[], aliases?: AdminRouteAliases): AdminRouteState;
25
+ /** Build a stable URL for one admin state. Fragment routing is the default. */
26
+ declare function adminRouteHref(current: URL, basePath: string, route: AdminRouteState, routing?: AdminRouting): string;
27
+ /** Backwards-compatible section helpers over the workspace router. */
28
+ declare function adminSectionFromUrl(url: URL, basePath: string, sectionIds: readonly string[]): string;
29
+ /** Build a compatibility URL for a top-level admin section. */
30
+ declare function adminSectionHref(current: URL, basePath: string, id: string, routing?: AdminRouting): string;
31
+
32
+ /** A workspace id or partial nested route passed to navigation helpers. */
33
+ type AdminRouteTarget = string | {
34
+ workspaceId?: string;
35
+ viewId?: string | null;
36
+ recordId?: string | null;
37
+ detailTab?: string | null;
38
+ };
39
+ /** Context shared by every admin workspace and its nested views. */
8
40
  interface AdminSectionContext {
9
41
  client: CrmClient;
10
42
  getToken: () => Promise<string | null>;
11
- /** Switch to another section by id (e.g. Overview "jump in" buttons). */
12
- navigate: (sectionId: string) => void;
43
+ route: AdminRouteState;
44
+ navigate: (target: AdminRouteTarget) => void;
45
+ href: (target: AdminRouteTarget) => string;
13
46
  }
14
- /** One admin-console section: a nav label and a body renderer. */
15
- interface AdminSection {
47
+ /** One top-level admin workspace rendered inside the signed-in shell. */
48
+ interface AdminWorkspace {
16
49
  id: string;
17
50
  label: string;
18
51
  render: (ctx: AdminSectionContext) => ReactNode;
19
52
  }
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. */
29
- type Brand = {
30
- name: string;
31
- badge?: string;
32
- wordmark?: ReactNode;
33
- };
34
- /** Resolve a section from the stable `?tab=` URL first, then a legacy path
35
- * segment for backwards-compatible deep links. */
36
- 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. */
39
- declare function adminSectionHref(current: URL, basePath: string, id: string, routing?: AdminRouting): string;
53
+ /** Legacy name for a top-level workspace. @deprecated Use AdminWorkspace. */
54
+ type AdminSection = AdminWorkspace;
55
+ /** Built-in shell presentation: editorial, legacy top bar, or host-owned. */
56
+ type AdminChrome = "editorial" | "topbar" | "none";
57
+ interface AdminShellProps {
58
+ workspaces: AdminWorkspace[];
59
+ basePath: string;
60
+ brand: Brand;
61
+ client: CrmClient;
62
+ getToken: () => Promise<string | null>;
63
+ signOut: () => void;
64
+ email: string | null;
65
+ routing?: AdminRouting;
66
+ chrome?: AdminChrome;
67
+ renderHeader?: (props: {
68
+ brand: Brand;
69
+ email: string | null;
70
+ signOut: () => void;
71
+ }) => ReactNode;
72
+ }
40
73
 
41
- /** Props for {@link ChapterAdmin}. */
74
+ /** Configuration for the Clerk-gated, brand-scoped Chapter admin console. */
42
75
  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
76
  chapter?: Chapter;
46
- /** Explicit admin sections. Omit with `chapter` for the standard console. */
77
+ /** Replace or transform the standard workspace set. */
78
+ workspaces?: AdminWorkspace[] | ((defaults: readonly AdminWorkspace[]) => AdminWorkspace[]);
79
+ /** @deprecated Use workspaces. */
47
80
  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
81
  basePath?: string;
51
- /** Wordmark shown in the gate + top bar. */
52
82
  brand?: Brand;
53
- /** CRM route mount. Default "/api/crm". */
54
83
  crmBasePath?: string;
55
- /** Origin for /api/config and /api/me. Default same origin. */
56
84
  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. */
85
+ /** Default fragment: `/admin/#people/person/record-id/profile`. */
60
86
  routing?: AdminRouting;
87
+ /** Default editorial. Topbar retains the legacy global-nav shell. */
88
+ chrome?: AdminChrome;
89
+ renderHeader?: AdminShellProps["renderHeader"];
61
90
  }
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
- */
91
+ /** Render the complete authenticated admin application from one Chapter config. */
69
92
  declare function ChapterAdmin(props: ChapterAdminProps): react.JSX.Element;
70
93
 
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. */
94
+ /** Default follower grammar: three workspaces with nested operational views. */
95
+ declare function defaultAdminWorkspaces(chapter: Chapter): AdminWorkspace[];
96
+
97
+ /** Legacy default catalog. @deprecated Use defaultAdminWorkspaces. */
74
98
  declare function defaultAdminSections(chapter: Chapter): AdminSection[];
75
99
 
76
- /** Options for {@link peopleSection}. */
100
+ /** Configuration for the person-collection compatibility preset. */
77
101
  interface PeopleSectionOptions {
78
- /** The chapter's resolved CRM engine — `chapter.crm` from defineChapter. */
79
102
  crm: Crm;
80
- /** Section id / route segment. Default "people". */
81
103
  id?: string;
82
- /** Nav label. Default "People". */
83
104
  label?: string;
84
- /** The record type to list. Default "person". */
85
105
  type?: string;
86
106
  }
87
- /** Options for {@link collectionSection}. */
107
+ /** Configuration for one CRM collection and its application-specific tabs. */
88
108
  interface CollectionSectionOptions {
89
- /** The chapter's resolved CRM engine — `chapter.crm` from defineChapter. */
90
109
  crm: Crm;
91
- /** The CRM record type to list (e.g. `"person"`, `"deal"`, `"investment"`). */
92
110
  type: string;
93
- /** Section id / route segment. Defaults to `type`. */
94
111
  id?: string;
95
- /** Nav label. Defaults to the type's plural label from the CRM config. */
96
112
  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
113
  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
114
  roles?: readonly string[];
115
+ networkSharing?: boolean;
116
+ extendRecordTabs?: (tabs: readonly RecordPanelTab[], context: CrmWorkspaceRecordContext) => readonly RecordPanelTab[];
104
117
  }
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
- */
118
+ /** Build one CRM collection as an admin workspace. */
115
119
  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
- */
120
+ /** Person-collection preset. */
121
121
  declare function peopleSection(options: PeopleSectionOptions & {
122
122
  roles?: readonly string[];
123
+ networkSharing?: boolean;
124
+ extendRecordTabs?: CollectionSectionOptions["extendRecordTabs"];
123
125
  }): AdminSection;
124
126
 
125
- /** Options for {@link dashboardSection}. */
127
+ /** Labels and ids for a composed dashboard workspace entry. */
126
128
  interface DashboardSectionOptions {
127
129
  id?: string;
128
130
  label?: string;
129
131
  }
130
- /** Build the Dashboard admin section over `GET /api/admin/dashboard`. */
132
+ /** Build the operational dashboard workspace over `/api/admin/dashboard`. */
131
133
  declare function dashboardSection(options?: DashboardSectionOptions): AdminSection;
132
134
 
133
135
  /** Options for {@link billingSection}. */
@@ -214,12 +216,11 @@ declare function Panel({ title, actions, children }: {
214
216
  actions?: ReactNode;
215
217
  children: ReactNode;
216
218
  }): 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. */
219
+ /** A loud banner when the odla-ui theme token layer is missing. */
219
220
  declare function ThemeWarning(): react.JSX.Element | null;
220
221
  /** A centered status line for loading / empty / error states. */
221
222
  declare function AdminNote({ children }: {
222
223
  children: ReactNode;
223
224
  }): react.JSX.Element;
224
225
 
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 };
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 };
@@ -7,6 +7,8 @@ import {
7
7
  RecordActions,
8
8
  ThemeWarning,
9
9
  adminFetch,
10
+ adminRouteFromUrl,
11
+ adminRouteHref,
10
12
  adminSectionFromUrl,
11
13
  adminSectionHref,
12
14
  availabilitySection,
@@ -14,12 +16,13 @@ import {
14
16
  collectionSection,
15
17
  dashboardSection,
16
18
  defaultAdminSections,
19
+ defaultAdminWorkspaces,
17
20
  emailSection,
18
21
  meetingsSection,
19
22
  peopleSection,
20
23
  useAdminResource
21
- } from "../../chunk-UIWFEESI.js";
22
- import "../../chunk-MKXHZMAP.js";
24
+ } from "../../chunk-AOTWFOH4.js";
25
+ import "../../chunk-3JG5X2LT.js";
23
26
  export {
24
27
  AdminNote,
25
28
  AdminPage,
@@ -29,6 +32,8 @@ export {
29
32
  RecordActions,
30
33
  ThemeWarning,
31
34
  adminFetch,
35
+ adminRouteFromUrl,
36
+ adminRouteHref,
32
37
  adminSectionFromUrl,
33
38
  adminSectionHref,
34
39
  availabilitySection,
@@ -36,6 +41,7 @@ export {
36
41
  collectionSection,
37
42
  dashboardSection,
38
43
  defaultAdminSections,
44
+ defaultAdminWorkspaces,
39
45
  emailSection,
40
46
  meetingsSection,
41
47
  peopleSection,
@@ -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 { 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';
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-CXDiDj_s.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,6 +22,8 @@ import {
22
22
  RecordActions,
23
23
  ThemeWarning,
24
24
  adminFetch,
25
+ adminRouteFromUrl,
26
+ adminRouteHref,
25
27
  adminSectionFromUrl,
26
28
  adminSectionHref,
27
29
  availabilitySection,
@@ -29,14 +31,15 @@ import {
29
31
  collectionSection,
30
32
  dashboardSection,
31
33
  defaultAdminSections,
34
+ defaultAdminWorkspaces,
32
35
  emailSection,
33
36
  meetingsSection,
34
37
  peopleSection,
35
38
  useAdminResource
36
- } from "../chunk-UIWFEESI.js";
39
+ } from "../chunk-AOTWFOH4.js";
37
40
  import {
38
41
  BrandStyle
39
- } from "../chunk-MKXHZMAP.js";
42
+ } from "../chunk-3JG5X2LT.js";
40
43
  export {
41
44
  AdminNote,
42
45
  AdminPage,
@@ -52,6 +55,8 @@ export {
52
55
  SlotPicker,
53
56
  ThemeWarning,
54
57
  adminFetch,
58
+ adminRouteFromUrl,
59
+ adminRouteHref,
55
60
  adminSectionFromUrl,
56
61
  adminSectionHref,
57
62
  availabilitySection,
@@ -61,6 +66,7 @@ export {
61
66
  dayKey,
62
67
  dayLabel,
63
68
  defaultAdminSections,
69
+ defaultAdminWorkspaces,
64
70
  emailSection,
65
71
  fmtDate,
66
72
  fmtMoney,
@@ -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-CXDiDj_s.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-3JG5X2LT.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) => {