@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.
@@ -32,8 +32,36 @@ interface Rule {
32
32
  }
33
33
  /** Namespace → rule set. */
34
34
  type DbRules = Record<string, Rule>;
35
- /** Brand tokens that theme the site: palette, fonts, wordmark, nav, logos. */
35
+ /** Typed semantic roles shared by the public site and application surfaces. */
36
+ interface ChapterBrandTokens {
37
+ background?: string;
38
+ surface?: string;
39
+ surface2?: string;
40
+ text?: string;
41
+ textMuted?: string;
42
+ textFaint?: string;
43
+ border?: string;
44
+ borderStrong?: string;
45
+ accent?: string;
46
+ accentStrong?: string;
47
+ accentSoft?: string;
48
+ onAccent?: string;
49
+ good?: string;
50
+ warn?: string;
51
+ danger?: string;
52
+ chart1?: string;
53
+ chart2?: string;
54
+ chartPositive?: string;
55
+ chartNegative?: string;
56
+ }
57
+ /** Brand tokens that theme the site: theme, semantic roles, fonts, and logos. */
36
58
  interface ChapterBrand {
59
+ /** Preloaded @odla-ai/ui theme name used by ThemeScope. */
60
+ theme?: string;
61
+ /** Admin/application color mode. Explicit light is the safe default. */
62
+ colorScheme?: "light" | "dark" | "system";
63
+ tokens?: ChapterBrandTokens;
64
+ tokensDark?: ChapterBrandTokens;
37
65
  /** Palette overrides written into styles.css :root (e.g. `{ moss: "#2F3E34" }`
38
66
  * or `--ui-*` token names). Applies in BOTH light and dark unless overridden
39
67
  * by {@link paletteDark}. */
@@ -67,7 +95,7 @@ interface ChapterNetworkTarget {
67
95
  id: string;
68
96
  /** Human-facing site name. Defaults to `id`. */
69
97
  name?: string;
70
- /** Absolute follower origin, e.g. `https://silver.example.com`. */
98
+ /** Absolute follower origin, e.g. `https://chapter.example.com`. */
71
99
  url: string;
72
100
  /** Leader-vault key holding this follower's share secret. Defaults to
73
101
  * `network_share_<id-with-underscores>`. */
@@ -138,10 +166,10 @@ interface ChapterScheduling {
138
166
  }
139
167
  /** How a signed-in user's role is determined.
140
168
  *
141
- * - `"claim"` reads a role ladder from a JWT claim (Silver & Salt's model:
169
+ * - `"claim"` reads a role ladder from a JWT claim (the member-site model:
142
170
  * `provisional → member → admin`). Admin is the last (highest) rung; the
143
171
  * read-only `superAdmins` tier sits above it.
144
- * - `"table"` gates on the deny-all `admins` allowlist (Built Not Found's model):
172
+ * - `"table"` gates on the deny-all `admins` allowlist (the hub model):
145
173
  * a binary "email is an admin or isn't", set only in odla Studio.
146
174
  *
147
175
  * Defaults: `"claim"` in `chapter` mode, `"table"` in `hub` mode. */
@@ -168,7 +196,7 @@ interface ResolvedAuth {
168
196
  }
169
197
  /** The application status pipeline. Which statuses exist, and the subsets a site
170
198
  * allows a call to be booked from / an application approved from. Defaults to
171
- * Silver & Salt's pipeline. Status never moves backwards (package-enforced). */
199
+ * the reference pipeline. Status never moves backwards (package-enforced). */
172
200
  interface ChapterPipeline {
173
201
  stages?: readonly string[];
174
202
  bookableFrom?: readonly string[];
@@ -185,8 +213,8 @@ interface ResolvedPipeline {
185
213
  }
186
214
  /** The application (join form) validation surface — which string fields are
187
215
  * required vs accepted, their max lengths, and the request body cap. Drives
188
- * submit validation + the CRM slot projection; defaults to Silver & Salt's form.
189
- * The `applications` schema attrs stay fixed (byte-equal to S&S); this is
216
+ * submit validation + the CRM slot projection; defaults to the reference form.
217
+ * The `applications` schema attrs stay fixed; this is
190
218
  * validation config, not schema generation. */
191
219
  interface ChapterApplication {
192
220
  required?: readonly string[];
@@ -258,7 +286,7 @@ interface ChapterConfig {
258
286
  /** `notificationEmail` required in `chapter` mode. */
259
287
  emails?: ChapterEmails;
260
288
  scheduling?: ChapterScheduling;
261
- /** Application status pipeline (stages + bookable/approvable subsets). Defaults to S&S's. */
289
+ /** Application status pipeline (stages + bookable/approvable subsets). */
262
290
  pipeline?: ChapterPipeline;
263
291
  /** Join-form validation (required/optional fields, max lengths, body cap). */
264
292
  application?: ChapterApplication;
@@ -32,8 +32,36 @@ interface Rule {
32
32
  }
33
33
  /** Namespace → rule set. */
34
34
  type DbRules = Record<string, Rule>;
35
- /** Brand tokens that theme the site: palette, fonts, wordmark, nav, logos. */
35
+ /** Typed semantic roles shared by the public site and application surfaces. */
36
+ interface ChapterBrandTokens {
37
+ background?: string;
38
+ surface?: string;
39
+ surface2?: string;
40
+ text?: string;
41
+ textMuted?: string;
42
+ textFaint?: string;
43
+ border?: string;
44
+ borderStrong?: string;
45
+ accent?: string;
46
+ accentStrong?: string;
47
+ accentSoft?: string;
48
+ onAccent?: string;
49
+ good?: string;
50
+ warn?: string;
51
+ danger?: string;
52
+ chart1?: string;
53
+ chart2?: string;
54
+ chartPositive?: string;
55
+ chartNegative?: string;
56
+ }
57
+ /** Brand tokens that theme the site: theme, semantic roles, fonts, and logos. */
36
58
  interface ChapterBrand {
59
+ /** Preloaded @odla-ai/ui theme name used by ThemeScope. */
60
+ theme?: string;
61
+ /** Admin/application color mode. Explicit light is the safe default. */
62
+ colorScheme?: "light" | "dark" | "system";
63
+ tokens?: ChapterBrandTokens;
64
+ tokensDark?: ChapterBrandTokens;
37
65
  /** Palette overrides written into styles.css :root (e.g. `{ moss: "#2F3E34" }`
38
66
  * or `--ui-*` token names). Applies in BOTH light and dark unless overridden
39
67
  * by {@link paletteDark}. */
@@ -67,7 +95,7 @@ interface ChapterNetworkTarget {
67
95
  id: string;
68
96
  /** Human-facing site name. Defaults to `id`. */
69
97
  name?: string;
70
- /** Absolute follower origin, e.g. `https://silver.example.com`. */
98
+ /** Absolute follower origin, e.g. `https://chapter.example.com`. */
71
99
  url: string;
72
100
  /** Leader-vault key holding this follower's share secret. Defaults to
73
101
  * `network_share_<id-with-underscores>`. */
@@ -138,10 +166,10 @@ interface ChapterScheduling {
138
166
  }
139
167
  /** How a signed-in user's role is determined.
140
168
  *
141
- * - `"claim"` reads a role ladder from a JWT claim (Silver & Salt's model:
169
+ * - `"claim"` reads a role ladder from a JWT claim (the member-site model:
142
170
  * `provisional → member → admin`). Admin is the last (highest) rung; the
143
171
  * read-only `superAdmins` tier sits above it.
144
- * - `"table"` gates on the deny-all `admins` allowlist (Built Not Found's model):
172
+ * - `"table"` gates on the deny-all `admins` allowlist (the hub model):
145
173
  * a binary "email is an admin or isn't", set only in odla Studio.
146
174
  *
147
175
  * Defaults: `"claim"` in `chapter` mode, `"table"` in `hub` mode. */
@@ -168,7 +196,7 @@ interface ResolvedAuth {
168
196
  }
169
197
  /** The application status pipeline. Which statuses exist, and the subsets a site
170
198
  * allows a call to be booked from / an application approved from. Defaults to
171
- * Silver & Salt's pipeline. Status never moves backwards (package-enforced). */
199
+ * the reference pipeline. Status never moves backwards (package-enforced). */
172
200
  interface ChapterPipeline {
173
201
  stages?: readonly string[];
174
202
  bookableFrom?: readonly string[];
@@ -185,8 +213,8 @@ interface ResolvedPipeline {
185
213
  }
186
214
  /** The application (join form) validation surface — which string fields are
187
215
  * required vs accepted, their max lengths, and the request body cap. Drives
188
- * submit validation + the CRM slot projection; defaults to Silver & Salt's form.
189
- * The `applications` schema attrs stay fixed (byte-equal to S&S); this is
216
+ * submit validation + the CRM slot projection; defaults to the reference form.
217
+ * The `applications` schema attrs stay fixed; this is
190
218
  * validation config, not schema generation. */
191
219
  interface ChapterApplication {
192
220
  required?: readonly string[];
@@ -258,7 +286,7 @@ interface ChapterConfig {
258
286
  /** `notificationEmail` required in `chapter` mode. */
259
287
  emails?: ChapterEmails;
260
288
  scheduling?: ChapterScheduling;
261
- /** Application status pipeline (stages + bookable/approvable subsets). Defaults to S&S's. */
289
+ /** Application status pipeline (stages + bookable/approvable subsets). */
262
290
  pipeline?: ChapterPipeline;
263
291
  /** Join-form validation (required/optional fields, max lengths, body cap). */
264
292
  application?: ChapterApplication;
@@ -1138,7 +1138,7 @@ function createStripeProvider(config) {
1138
1138
  "expand[0]": "latest_invoice.confirmation_secret",
1139
1139
  metadata: meta
1140
1140
  },
1141
- // The hardening S&S lacked: one subscription per application, so a client
1141
+ // Hardening: one subscription per application, so a client
1142
1142
  // retry between create and the db write can't orphan a second one.
1143
1143
  `sub:${input.applicationId}`
1144
1144
  );
@@ -1709,18 +1709,30 @@ var handleAdminPeopleRole = async (req, url, env, ctx) => {
1709
1709
  };
1710
1710
 
1711
1711
  // src/series.ts
1712
- function bucketSeries(points, now, weeks = 12) {
1713
- const WEEK = 7 * 864e5;
1714
- const end = now;
1715
- const start = end - weeks * WEEK;
1716
- const buckets = Array.from({ length: weeks }, (_, i) => ({ weekStart: start + i * WEEK, value: 0 }));
1717
- for (const p of points) {
1718
- if (!Number.isFinite(p.t) || p.t < start || p.t > end) continue;
1719
- const idx = Math.min(weeks - 1, Math.floor((p.t - start) / WEEK));
1720
- const bucket = buckets[idx];
1721
- if (bucket) bucket.value += Number.isFinite(p.v) ? p.v : 0;
1722
- }
1723
- return buckets;
1712
+ function periodSeries(points, now, count, periodMs) {
1713
+ const start = now - count * periodMs;
1714
+ const previousStart = start - count * periodMs;
1715
+ const current = Array.from({ length: count }, () => 0);
1716
+ const previous = Array.from({ length: count }, () => 0);
1717
+ for (const point of points) {
1718
+ if (!Number.isFinite(point.t) || !Number.isFinite(point.v)) continue;
1719
+ if (point.t >= start && point.t <= now) {
1720
+ const index = Math.min(count - 1, Math.floor((point.t - start) / periodMs));
1721
+ current[index] = (current[index] ?? 0) + point.v;
1722
+ } else if (point.t >= previousStart && point.t < start) {
1723
+ const index = Math.min(count - 1, Math.floor((point.t - previousStart) / periodMs));
1724
+ previous[index] = (previous[index] ?? 0) + point.v;
1725
+ }
1726
+ }
1727
+ const labels = current.map((_, index) => new Date(start + index * periodMs).toISOString().slice(5, 10));
1728
+ return { labels, current, previous };
1729
+ }
1730
+ function dashboardMetricData(points, now) {
1731
+ const DAY = 864e5;
1732
+ return {
1733
+ wow: periodSeries(points, now, 7, DAY),
1734
+ mom: periodSeries(points, now, 4, 7 * DAY)
1735
+ };
1724
1736
  }
1725
1737
  function subAnnualCents(sub) {
1726
1738
  const items = sub.items?.data ?? [];
@@ -1803,8 +1815,8 @@ var handleAdminDashboard = async (req, url, env, ctx) => {
1803
1815
  if (subsRes.ok) {
1804
1816
  const subs = rows(subsRes.body.data);
1805
1817
  const subMs = (s) => (s.created ?? 0) * 1e3;
1806
- membersSeries = bucketSeries(subs.map((s) => ({ t: subMs(s), v: 1 })), now);
1807
- revenueSeries = bucketSeries(subs.map((s) => ({ t: subMs(s), v: subAnnualCents(s) })), now);
1818
+ membersSeries = dashboardMetricData(subs.map((s) => ({ t: subMs(s), v: 1 })), now);
1819
+ revenueSeries = dashboardMetricData(subs.map((s) => ({ t: subMs(s), v: subAnnualCents(s) })), now);
1808
1820
  const active = subs.filter((s) => s.status === "active");
1809
1821
  revenue = {
1810
1822
  billingReady: true,
@@ -1816,7 +1828,7 @@ var handleAdminDashboard = async (req, url, env, ctx) => {
1816
1828
  };
1817
1829
  }
1818
1830
  }
1819
- const applicationsSeries = bucketSeries(apps.map((a) => ({ t: a.createdAt || 0, v: 1 })), now);
1831
+ const applicationsSeries = dashboardMetricData(apps.map((a) => ({ t: a.createdAt || 0, v: 1 })), now);
1820
1832
  return json({ applications, applicationsSeries, pipeline, pipelineDelta, calls, agenda, timezone, revenue, revenueSeries, membersSeries });
1821
1833
  };
1822
1834
  var handleAdminBilling = async (req, url, env, ctx) => {