@odla-ai/chapter 0.51.0 → 0.52.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.
- package/README.md +102 -8
- package/dist/{chunk-4EA5V4JE.js → chunk-7N4VDWM5.js} +239 -136
- package/dist/chunk-7N4VDWM5.js.map +1 -0
- package/dist/{chunk-UAXWA5BL.js → chunk-7RIT2UN5.js} +2 -2
- package/dist/{chunk-JXX4BFDP.js → chunk-A6W53GVM.js} +5 -1
- package/dist/chunk-A6W53GVM.js.map +1 -0
- package/dist/{copy-context-Ddat__H5.d.ts → copy-context-CYLlPzAc.d.ts} +11 -5
- package/dist/index.cjs +88 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +70 -12
- package/dist/index.d.ts +70 -12
- package/dist/index.js +88 -7
- package/dist/index.js.map +1 -1
- package/dist/ui/admin/index.d.ts +14 -3
- package/dist/ui/admin/index.js +4 -2
- package/dist/ui/index.d.ts +2 -2
- package/dist/ui/index.js +5 -3
- package/dist/ui/member/index.d.ts +2 -2
- package/dist/ui/member/index.js +2 -2
- package/dist/worker/index.cjs +912 -354
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +47 -9
- package/dist/worker/index.d.ts +47 -9
- package/dist/worker/index.js +912 -354
- package/dist/worker/index.js.map +1 -1
- package/package.json +2 -1
- package/dist/chunk-4EA5V4JE.js.map +0 -1
- package/dist/chunk-JXX4BFDP.js.map +0 -1
- /package/dist/{chunk-UAXWA5BL.js.map → chunk-7RIT2UN5.js.map} +0 -0
package/README.md
CHANGED
|
@@ -185,6 +185,25 @@ This carries the compiler's complete light map, derived dark map, and invert
|
|
|
185
185
|
map into Chapter without adding an `@odla-ai/brand` runtime dependency to
|
|
186
186
|
Chapter itself.
|
|
187
187
|
|
|
188
|
+
#### Packaged workspaces are styled by Chapter, through `@odla-ai/ui`
|
|
189
|
+
|
|
190
|
+
Chapter owns the styling of every workspace it ships (Admission, Builder
|
|
191
|
+
Library, People, Settings, and the rest) and does it only through `@odla-ai/ui`
|
|
192
|
+
form components and the `--ui-*` tokens the active theme scope supplies. A
|
|
193
|
+
packaged picker is always the `@odla-ai/ui` `Select`, a native select carrying
|
|
194
|
+
the `select` class, so it takes the host's theme without any host CSS, keeps
|
|
195
|
+
its accessible name through its `Field` label, and keeps native keyboard
|
|
196
|
+
behaviour. A guard test in the package fails if a bare select tag reappears in
|
|
197
|
+
a Chapter UI source.
|
|
198
|
+
|
|
199
|
+
The boundary runs the other way too. A host must not patch package-owned
|
|
200
|
+
markup with its own CSS: if a packaged control looks browser-default, the theme
|
|
201
|
+
layer is missing (see the banner above) or the package has a bug to report,
|
|
202
|
+
never a selector to override. Any custom picker a host adds beside a packaged
|
|
203
|
+
workspace should be the same `@odla-ai/ui` `Select` (with `Field` for its
|
|
204
|
+
label) rather than a bare element, so host and package controls read as one
|
|
205
|
+
surface.
|
|
206
|
+
|
|
188
207
|
## API quick start
|
|
189
208
|
|
|
190
209
|
```ts
|
|
@@ -932,6 +951,32 @@ removing it changes no customer-facing behavior. The BNF frontend may be down
|
|
|
932
951
|
while the webhook continues to ingest. Registering a Stripe endpoint and
|
|
933
952
|
enabling production remain separate reviewed operations.
|
|
934
953
|
|
|
954
|
+
A second delivery of a retained event id is a replay when its digest matches, or
|
|
955
|
+
when its canonical fact (id, type, created, and the allowlisted `data.object`
|
|
956
|
+
fields) equals the retained fact however the delivery envelope changed. A
|
|
957
|
+
canonical replay also resolves an open collision for that event, recording a
|
|
958
|
+
`paymentRecoveryReviews` row with the system as actor, unless the collision
|
|
959
|
+
recorded a different fact. Only a delivery whose fact differs registers a
|
|
960
|
+
collision, and it stays active through rebuilds until an operator decides:
|
|
961
|
+
`POST` with `{ command: "resolve", eventId, mutationId, reason }` records the
|
|
962
|
+
actor and reason, dismisses the colliding delivery so the same one replays
|
|
963
|
+
quietly, and keeps the retained fact as the evidence of record; it refuses
|
|
964
|
+
without a valid `mutationId` and a reason. `POST` with
|
|
965
|
+
`{ command: "recover-event", eventId }` instead trusts an independent read of
|
|
966
|
+
the provider Event and rewrites the retained digest when every fact matches.
|
|
967
|
+
|
|
968
|
+
`/api/admin/payment-recovery` is the drain on both sides. A chapter lists and
|
|
969
|
+
retries its quarantined Stripe receipts; a hub that owns the read model lists
|
|
970
|
+
every active commercial exception (`count` plus provider ids, no PII) and
|
|
971
|
+
retries one through the same ingest the signed webhook uses after an
|
|
972
|
+
independent provider read, with a review row per retry.
|
|
973
|
+
|
|
974
|
+
Both Workers raise an error-level operator alert when a receipt enters
|
|
975
|
+
quarantine or a commercial exception becomes active, carrying the current count
|
|
976
|
+
and provider event ids only. Pass `recordError` from `@odla-ai/o11y` as
|
|
977
|
+
`chapterWorker({ chapter, recordError })` to deliver those alerts to o11y; the
|
|
978
|
+
default is one structured `console.error` line per alert.
|
|
979
|
+
|
|
935
980
|
`POST` with `{ command: "reconcile-chapters" }` is a separate, two-sided opt-in
|
|
936
981
|
comparison. A hub target and the matching follower reader must both set
|
|
937
982
|
`commercialParity: true`; neither record browsing nor Stripe shadow mode grants
|
|
@@ -1107,10 +1152,15 @@ name is public configuration, not a credential. If a configured binding is
|
|
|
1107
1152
|
missing at runtime, the target fails explicitly instead of falling back to a
|
|
1108
1153
|
potentially misrouted public fetch.
|
|
1109
1154
|
|
|
1110
|
-
|
|
1111
|
-
`network_share_example_chapter` in the leader (or
|
|
1112
|
-
`secretName`)
|
|
1113
|
-
|
|
1155
|
+
Pair the edge by vaulting one generated value as `network_share_secret` in the
|
|
1156
|
+
follower and as `network_share_example_chapter` in the leader (or the target's
|
|
1157
|
+
`secretName`), never printed. The integration descriptor declares both slots,
|
|
1158
|
+
so provisioning tooling can hold them against the vault, and until both hold a
|
|
1159
|
+
value every leader-side surface reports the edge as `code: "edge_unpaired"`
|
|
1160
|
+
with a `hint` naming the CLI pairing command (`chapter edge pair`, which
|
|
1161
|
+
writes both slots from one generated value and ships with the next
|
|
1162
|
+
`@odla-ai/cli` release). No secret is placed in source, browser data, or
|
|
1163
|
+
provisioning config. The standard collection drawer discovers the target and calls the
|
|
1114
1164
|
admin-gated push route. A follower must declare the receiving CRM type/fields;
|
|
1115
1165
|
otherwise it rejects the record cleanly instead of dropping fields.
|
|
1116
1166
|
|
|
@@ -1234,8 +1284,11 @@ Use distinct development tenants and follower origins for the first delivery:
|
|
|
1234
1284
|
|
|
1235
1285
|
1. Confirm every target `id` exactly equals the follower's `chapter.id`. For
|
|
1236
1286
|
Cloudflare Workers, confirm its `binding` exists in the leader's development
|
|
1237
|
-
Wrangler environment.
|
|
1238
|
-
the follower and under the target's resolved
|
|
1287
|
+
Wrangler environment. Pair the edge: vault one generated value as
|
|
1288
|
+
`network_share_secret` in the follower and under the target's resolved
|
|
1289
|
+
`secretName` in the leader (the CLI's `chapter edge pair` command does both
|
|
1290
|
+
writes from the next `@odla-ai/cli` release on). The leader rollup reports
|
|
1291
|
+
`edge_unpaired` until both slots hold a value.
|
|
1239
1292
|
2. Confirm `GET /api/admin/network/targets` lists the development follower with
|
|
1240
1293
|
the expected compatible record types.
|
|
1241
1294
|
3. Share one test person and one test business from the leader's record drawer.
|
|
@@ -1464,13 +1517,38 @@ These bite silently — a smoke test won't catch them:
|
|
|
1464
1517
|
| Template | Fires from | Configurable |
|
|
1465
1518
|
|---|---|---|
|
|
1466
1519
|
| `adminNotification` | `POST /api/applications` (submit) — or the Stripe webhook (first payment) | `sends.adminNotification: "submit" \| "payment" \| "never"` (default `"submit"`) |
|
|
1467
|
-
| `
|
|
1520
|
+
| `submitConfirmation` | `POST /api/applications` (submit) — a free tier always; a paid tier only when `paymentConfirmation` is disabled, so a paid applicant is confirmed once | template `enabled` flag |
|
|
1521
|
+
| `prepEmail` | `POST /api/schedule/book` — once per application; a reschedule sends no second one | template `enabled` flag |
|
|
1468
1522
|
| `paymentConfirmation` | Stripe webhook, first successful invoice | template `enabled` flag |
|
|
1523
|
+
| `seatAccepted` | `POST /api/named-seat/accept` — to the seat's purchaser, once per seat, naming the recipient and tier | template `enabled` flag; the seeded default copy applies until the group row carries its own `emailTemplates.seatAccepted` |
|
|
1469
1524
|
|
|
1470
1525
|
```ts
|
|
1471
1526
|
defineChapter({ /* … */ sends: { adminNotification: "payment" } });
|
|
1472
1527
|
```
|
|
1473
1528
|
|
|
1529
|
+
**Template variables.** Every value comes from stored data — the frozen
|
|
1530
|
+
checkout the applicant consented to, the tier catalog, the booked meeting,
|
|
1531
|
+
the request origin — never from a price or a name written into code. An
|
|
1532
|
+
unknown placeholder renders empty, so copy can use any subset.
|
|
1533
|
+
|
|
1534
|
+
| Variable | Templates | Value |
|
|
1535
|
+
|---|---|---|
|
|
1536
|
+
| `firstName`, `lastName`, `email`, `phone`, `state` | all | the application row |
|
|
1537
|
+
| `tier`, `tierName` | `adminNotification`, `submitConfirmation`, `prepEmail` | the frozen checkout quote's tier name, else the catalog name for `applications.tierId` |
|
|
1538
|
+
| `paymentAmount`, `paymentAmountSummary` | `submitConfirmation`, `paymentConfirmation`, `prepEmail` | `$900.00` and ` of $900.00` from the frozen checkout; both blank for a free tier or before checkout |
|
|
1539
|
+
| `submitNextStep` | `submitConfirmation` | "Next, schedule your introduction call." / "Next, complete your membership payment." / "Your membership is approved." |
|
|
1540
|
+
| `paymentNextStep` | `paymentConfirmation` | the post-payment step (schedule, waived, or approved) |
|
|
1541
|
+
| `startAt`, `endAt`, `timezone` | `prepEmail` | the booked call rendered in the group's `scheduling.timezone`, e.g. `Wednesday, October 7, 2026 at 10:00 AM EDT` |
|
|
1542
|
+
| `meetUrl`, `htmlLink` | `prepEmail` | the booked meeting's Google Meet and calendar links (blank when the provider returned none) |
|
|
1543
|
+
| `meetingTime`, `meetingLink` | `prepEmail` | aliases of `startAt` and `meetUrl` (falling back to `htmlLink`) for copy written before those existed |
|
|
1544
|
+
| `adminUrl`, `membersUrl` | `adminNotification`, `submitConfirmation`, `onboardingInvite` | `<origin>/admin/` and `<origin>/members/` from the request that fired the send |
|
|
1545
|
+
| `refundPolicyText`, `commitmentText`, `normsText` | all | the group row's policy copy |
|
|
1546
|
+
|
|
1547
|
+
**From display name.** `emails.fromName` (seeded to the group row, editable
|
|
1548
|
+
in Settings → Email) turns the From header into `{ name, email }` —
|
|
1549
|
+
`Tori Horton <tori@…>` in an inbox — while the address stays the Worker's
|
|
1550
|
+
`EMAIL_FROM`. A group with no name sends the bare address exactly as before.
|
|
1551
|
+
|
|
1474
1552
|
**Re-check this on every chapter upgrade.** Wiring a send changes a site's
|
|
1475
1553
|
outbound mail with no local diff — release notes call out send changes
|
|
1476
1554
|
explicitly for that reason.
|
|
@@ -1634,7 +1712,11 @@ These bite silently — a smoke test won't catch them:
|
|
|
1634
1712
|
`drainMembershipEffects` recovers pending provider work and cancels unpaid
|
|
1635
1713
|
seats within one exact configured runtime. It never refunds an unaccepted
|
|
1636
1714
|
seat: a purchased seat waits for its recipient for the whole paid term, and a
|
|
1637
|
-
refund is a super admin's decision on request.
|
|
1715
|
+
refund is a super admin's decision on request. When the recipient accepts,
|
|
1716
|
+
the chapter emails the purchaser one `seatAccepted` notice with the
|
|
1717
|
+
recipient's name and tier, deduplicated per seat through the email log; a
|
|
1718
|
+
chapter edits or disables it on its group row, and the default copy applies
|
|
1719
|
+
until then. Refunding the recipient's own
|
|
1638
1720
|
application (`POST /api/admin/applications/:id/refund`) requires a super
|
|
1639
1721
|
admin when it carries a gifted seat. On a chapter,
|
|
1640
1722
|
`GET /api/admin/applications/:id/named-seat` shows a purchaser's seat and
|
|
@@ -1680,6 +1762,18 @@ These bite silently — a smoke test won't catch them:
|
|
|
1680
1762
|
read the same field instead of hard-coding a founding price. The issued offer
|
|
1681
1763
|
at checkout remains the only promise.
|
|
1682
1764
|
|
|
1765
|
+
On the network owner, `GET /api/admin/membership-policy-versions` (super
|
|
1766
|
+
admin; `?chapterId=` narrows it to one chapter) lists every offering and
|
|
1767
|
+
discount policy version the authority holds, newest first, with the state
|
|
1768
|
+
issuance would give it now (active, scheduled, superseded with the date it
|
|
1769
|
+
was passed over, draft, retired), its effective or qualifying window, the
|
|
1770
|
+
provider mechanism a discount uses, and whatever `source`, `actor` and
|
|
1771
|
+
`recordedAt` the writing host stored on the row. `MembershipPolicyVersions`
|
|
1772
|
+
from `@odla-ai/chapter/ui/admin` renders it read-only beside
|
|
1773
|
+
`NetworkSeatBilling`, so an operator can see why a card shows a list price
|
|
1774
|
+
and a payable price without reading the database; nothing on it edits a
|
|
1775
|
+
version.
|
|
1776
|
+
|
|
1683
1777
|
On a chapter, a person record with no linked application — someone invited or
|
|
1684
1778
|
a candidate — is managed by the chapter's own admins the way the network
|
|
1685
1779
|
owner manages it: the record's Invitation tab sets or revokes the local
|