@odla-ai/chapter 0.20.0 → 0.20.1

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.
Files changed (2) hide show
  1. package/README.md +122 -5
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -154,10 +154,10 @@ export default {
154
154
  ```
155
155
 
156
156
  That is the reusable application shell. The follower's public pages remain
157
- site-owned: start from the Silver & Salt content rhythm and `@odla-ai/ui`
158
- marketing components, then change the wordmark, palette, copy, imagery, and
159
- investment thesis. Do not fork auth, admin routing, CRM, payment, booking, or
160
- account logic to achieve a different brand.
157
+ site-owned: start from a reference design and `@odla-ai/ui` marketing
158
+ components, then change the wordmark, palette, copy, imagery, and investment
159
+ thesis. Do not fork auth, admin routing, CRM, payment, booking, or account logic
160
+ to achieve a different brand.
161
161
 
162
162
  ### Leader → follower delivery
163
163
 
@@ -190,9 +190,126 @@ config. The standard collection drawer discovers the target and calls the
190
190
  admin-gated push route. A follower must declare the receiving CRM type/fields;
191
191
  otherwise it rejects the record cleanly instead of dropping fields.
192
192
 
193
+ #### Existing follower CRMs must opt into the shared graph
194
+
195
+ When `crm` is omitted, chapter's default already contains compatible `person`
196
+ and `company` types plus a `works_at` relation. Passing a custom CRM replaces
197
+ that default; chapter does not merge missing types or fields into it. A follower
198
+ that wants both people and businesses must therefore declare compatible types
199
+ itself:
200
+
201
+ ```ts
202
+ import { defineCrm } from "@odla-ai/crm";
203
+
204
+ const crm = defineCrm({
205
+ types: {
206
+ person: {
207
+ label: "Person",
208
+ labelPlural: "People",
209
+ nameField: "name",
210
+ emailField: "email",
211
+ fields: {
212
+ name: { type: "string", label: "Name", required: true },
213
+ email: { type: "email", label: "Email" },
214
+ firstName: { type: "string", label: "First name" },
215
+ lastName: { type: "string", label: "Last name" },
216
+ phone: { type: "string", label: "Phone" },
217
+ linkedin: { type: "string", label: "LinkedIn" },
218
+ },
219
+ },
220
+ company: {
221
+ label: "Business",
222
+ labelPlural: "Businesses",
223
+ nameField: "name",
224
+ fields: {
225
+ name: { type: "string", label: "Name", required: true },
226
+ domain: { type: "string", label: "Domain / website", slot: "s1" },
227
+ industry: { type: "string", label: "Industry" },
228
+ location: { type: "string", label: "Location" },
229
+ linkedin: { type: "string", label: "LinkedIn" },
230
+ notes: { type: "string", label: "Notes" },
231
+ },
232
+ },
233
+ },
234
+ relations: {
235
+ works_at: {
236
+ from: "person",
237
+ to: "company",
238
+ label: "works at",
239
+ reverseLabel: "team",
240
+ },
241
+ },
242
+ });
243
+ ```
244
+
245
+ The follower must declare every field its leader may send. Unknown types or
246
+ fields fail the request before any CRM write. Record delivery currently moves
247
+ records, not `crm_link` relation rows; create or curate `works_at` links locally.
248
+
249
+ #### Custom leader consoles must mount the sharing UI
250
+
251
+ Automatic “Share with …” actions come from chapter's standard collection
252
+ sections. They are present when the console uses
253
+ `<ChapterAdmin chapter={chapter} />`. Passing an explicit `sections` array
254
+ replaces that catalog, so a custom console must deliberately compose it:
255
+
256
+ ```tsx
257
+ import {
258
+ ChapterAdmin,
259
+ defaultAdminSections,
260
+ } from "@odla-ai/chapter/ui/admin";
261
+
262
+ const sections = [
263
+ customOverviewSection,
264
+ ...defaultAdminSections(chapter),
265
+ ];
266
+
267
+ render(
268
+ <ChapterAdmin chapter={chapter} sections={sections} />,
269
+ document.getElementById("admin-root"),
270
+ );
271
+ ```
272
+
273
+ If the custom console renders its own record drawer instead, mount
274
+ `NetworkShareActions` inside that drawer:
275
+
276
+ ```tsx
277
+ <NetworkShareActions
278
+ recordId={record.id}
279
+ recordType={record.type}
280
+ getToken={sectionContext.getToken}
281
+ />
282
+ ```
283
+
284
+ The component discovers compatible targets through
285
+ `GET /api/admin/network/targets`; it never receives follower secrets in browser
286
+ data.
287
+
288
+ #### Verify delivery in development before production
289
+
290
+ Use distinct development tenants and follower origins for the first delivery:
291
+
292
+ 1. Vault one random value as `network_share_secret` in the follower and under
293
+ the target's resolved `secretName` in the leader.
294
+ 2. Confirm `GET /api/admin/network/targets` lists the development follower with
295
+ the expected compatible record types.
296
+ 3. Share one test person and one test business from the leader's record drawer.
297
+ Confirm each appears in the follower with only allowlisted fields.
298
+ 4. Share each record again. The second delivery must update the same follower
299
+ record, not create a duplicate.
300
+ 5. Confirm the follower's pipeline, account, and billing state did not change;
301
+ those remain locally authoritative.
302
+ 6. Change one allowlisted leader field and share again to prove later deliveries
303
+ update the existing record. A target with the wrong secret must return `401`
304
+ without writing CRM data.
305
+
306
+ Only after this contract passes against development origins should the leader
307
+ target be changed to a production follower origin and the matching production
308
+ vault values be installed.
309
+
193
310
  Section navigation defaults to `/admin/?tab=people`, not
194
311
  `/admin/people`. Query routing deliberately works in both leader-style SPAs and
195
- Silver & Salt-style static subdirectories. Legacy path links still open, and
312
+ ordinary static subdirectories. Legacy path links still open, and
196
313
  `routing="path"` remains available only for hosts with an explicit SPA fallback.
197
314
 
198
315
  ## Adopting into an existing site
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@odla-ai/chapter",
3
- "version": "0.20.0",
3
+ "version": "0.20.1",
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",