@ai-matrx/associations 0.5.2 → 0.6.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,114 @@
1
1
  # Changelog — @ai-matrx/associations
2
2
 
3
+ ## 0.6.1 — 2026-08-30 (vocabulary catch-up: commerce_print_order)
4
+
5
+ Regenerated `src/entity-types.generated.ts` from live `platform.entity_types`:
6
+ **655 → 656 tokens**, adding `commerce_print_order` (`commerce.print_order`,
7
+ base tier 1, listed, scopeable) — the print-order entity the @ai-matrx/print
8
+ work landed in the database.
9
+
10
+ WHY THIS IS A RELEASE AND NOT A LOCAL REGENERATION: the vocabulary ships IN
11
+ this package; consumers have no local regeneration path. So a token that
12
+ exists live but not in the published package is not a cosmetic lag — it fails
13
+ `gen:entity-types` in every consumer, and matrx-frontend's release script runs
14
+ that generator as a gate. This one missing token blocked EVERY frontend
15
+ release (reproduced 2026-08-30: `release.sh` refused at "Installed
16
+ @ai-matrx/associations vocabulary (655 tokens) is OUT OF SYNC with
17
+ platform.entity_types (656 live tokens)"). The vocabulary has to be published
18
+ as promptly as the migration that adds a token.
19
+
20
+ No API change. 187 tests and typecheck pass unchanged.
21
+
22
+ ## 0.6.0 — 2026-08-30 (C23 — every port ships a working default; the split entry)
23
+
24
+ THE ALL-INCLUSIVE LAW retrofit (register row: associations). The audit found
25
+ seven ports a host had to fill, four UI ports with NO shipped default, and the
26
+ bundle-split concern pushed onto the host as hand-written `next/dynamic` glue.
27
+ All of it now lives in the package.
28
+
29
+ ### Ports: defaults, not degradations
30
+
31
+ - **`windowShell` → `DefaultWindowShell` (new).** The absent-port path was a
32
+ fixed, non-draggable card. It is now a REAL window: drag by the header,
33
+ resize from a labelled grip, maximize/restore, viewport-clamped on drag and
34
+ on viewport resize (a window can never be dragged somewhere it cannot be
35
+ dragged back from), Escape-to-close, portalled to `<body>` (a transformed
36
+ ancestor can no longer clip it), non-modal, and a full-width bottom card
37
+ under 640px. Exported as `DefaultWindowShell` for direct use.
38
+ - **`errorSink` is no longer REQUIRED.** `createAssociationsStore` ships
39
+ `createDefaultErrorSink()` (new, `/core`): a tagged `console.error` per
40
+ event that ANNOUNCES ONCE that no sink is bound and names the remedy — the
41
+ default is louder about itself than the old hard failure was. Only
42
+ `dataSource` and `identity` still throw; they ARE app identity.
43
+ - **`entityDoors.DoorControls` → `DoorControlsSlot` (new).** Every face now
44
+ renders doors through one slot: the host port when bound, otherwise a real,
45
+ keyboard-reachable, `aria-label`led `target="_blank"` anchor built from the
46
+ registry overlay's `hrefFor`. A token with no registered route renders
47
+ nothing — honest absence, never a control that goes nowhere.
48
+ - **`capture` → the whole upload flow over one injected primitive.** Bind
49
+ `capture.uploadFile(file, { folderPath, visibility })` and the package ships
50
+ everything else: the file input, whole-body drag-and-drop, sequential
51
+ uploads, the stall watchdog, per-file outcomes, and the loud
52
+ created-but-unattached report. `requestUpload` remains for a host that owns
53
+ a richer pipeline and still wins when both are bound.
54
+ - **`pickerOverrides` needs no default** — the in-package `AssociationPicker`
55
+ IS the default for every token, and it is complete (candidate list, search,
56
+ attach/detach, the create-then-associate "+ New" footer).
57
+ - **`notifier` degradation unchanged** (warn-once, failures always reach the
58
+ errorSink). The package deliberately does NOT ship a toast stack: a toast
59
+ surface needs a host-mounted root, so a "default" would be the silent
60
+ no-op this package exists to prevent.
61
+
62
+ ### The bundle split is package-owned
63
+
64
+ - **`@ai-matrx/associations/react/lazy` (new subpath, 3.4 KB)** — every heavy
65
+ face behind a real lazy edge: `AssociationWindow`, `AssociationPicker`,
66
+ `UniversalAssociationPicker`, `AttachedItemsSheet`, `AssociationCard`,
67
+ `AssociationCardGrid`, `AssociationList`, `AssociationCaptureToolbar`,
68
+ `AssociationEntitySelect`, `CategorySelect`, `CategoryTagPicker`,
69
+ `CommentThread`. The loader dynamic-imports this package's own `./react`
70
+ subpath by name (external in our build), so the CONSUMER's bundler emits the
71
+ chunk. Framework-agnostic — `React.lazy` + `Suspense`, no `next/dynamic`,
72
+ no `next` dependency.
73
+ - **`lazyWindowShell(loader)` / `lazyPickerOverride(loader)`** (`/react`) —
74
+ the same seam for a HOST component bound to a port, so a host stops
75
+ hand-writing `next/dynamic({ ssr: false })` glue per binding.
76
+ `lazyPickerOverride` additionally gates on `open`, so an override never
77
+ fetches its chunk until its picker is opened.
78
+
79
+ ### Hard parts moved in (C22)
80
+
81
+ - **The demanded-schema boot probe runs in `AssociationsProvider`** — ON in a
82
+ development build, off elsewhere, overridable with `probeSchema`. Every host
83
+ was writing this effect itself (dynamic import, try/catch, hand-rolled
84
+ `demanded_schema_violation` scream). The store now also exposes the bound
85
+ `dataSource` so nothing has to be re-exported to run it.
86
+ - **`useAssociationPickerBridge(props, opts)` (new, `/react`)** — the
87
+ attach-batch / toggle engine a per-token picker override needs: loud partial
88
+ outcomes, created-but-unattached copy naming where the item lives, refusal
89
+ screams on both attach and detach (the inert-click bug class, feedback
90
+ 35d311a9), and errorSink routing. Overrides now write two calls instead of
91
+ re-implementing failure semantics.
92
+
93
+ ### Consumer action
94
+
95
+ - **Nothing breaks.** `errorSink` moving from required to optional is
96
+ source-compatible; every existing binding keeps working unchanged.
97
+ - **Delete host `next/dynamic` glue** around associations port bindings and
98
+ use `lazyWindowShell` / `lazyPickerOverride` from `@ai-matrx/associations/react`.
99
+ - **Delete the host demanded-schema probe effect** (the `assertDemandedSchema`
100
+ boot check) — the provider owns it. Pass `probeSchema={false}` to opt out.
101
+ - **Delete hand-written attach/detach failure handling** inside picker
102
+ overrides and bind `useAssociationPickerBridge` instead.
103
+ - **A host that binds `windowShell` should re-decide whether it needs to**:
104
+ bind it only for a real window MANAGER (tray docking, workspace
105
+ persistence, cross-window z-order). Otherwise drop the binding and take
106
+ `DefaultWindowShell`.
107
+ - **A host with no upload pipeline** can now get the whole capture flow by
108
+ binding only `capture.uploadFile`.
109
+ - New optional peer `react-dom` (`>=18.0.0`) — the default window portals.
110
+ Any React DOM host already has it.
111
+
3
112
  ## 0.5.2 — 2026-08-30 (X3 peer retrofit)
4
113
 
5
114
  - `@ai-matrx/design-system` is now a REAL dependency (`^0.3.1`) instead of an
package/README.md CHANGED
@@ -32,7 +32,7 @@ import {
32
32
  const store = createAssociationsStore({
33
33
  dataSource: supabase, // structural subset of SupabaseClient
34
34
  identity: { requireUserId }, // throws when unauthenticated
35
- errorSink: (e) => report(e), // recovery layers scream here
35
+ errorSink: (e) => report(e), // OPTIONAL default screams to console
36
36
  entityOverlay: { file: { hrefFor: (id) => `/files/f/${id}` } }, // optional
37
37
  });
38
38
 
@@ -43,7 +43,8 @@ store.titles.prime("task", taskId, "Renamed");
43
43
  await store.favorites.setFavorite("note", noteId, true);
44
44
 
45
45
  // Dev/CI boot check for any new environment (zero side effects; selfTest
46
- // proves the probe can fail before you trust its green):
46
+ // proves the probe can fail before you trust its green). You normally never
47
+ // call this: <AssociationsProvider> runs it for you in a development build.
47
48
  await assertDemandedSchema(supabase, { selfTest: true });
48
49
  ```
49
50
 
@@ -61,10 +62,10 @@ import {
61
62
  <AssociationsProvider
62
63
  store={store} // the /core store from createAssociationsStore
63
64
  notifier={toastAdapter} // optional — absent warns once, failures still hit errorSink
64
- windowShell={windowAdapter} // optional — absent falls back to a fixed overlay
65
- capture={captureHandlers} // optional, per-handler chips render only when bound
66
- pickerOverrides={{ file: FilePickerWindowAdapter }} // optional per-token pickers
67
- entityDoors={{ EntityRef, DoorControls, UnresolvedRef }} // optional doors
65
+ windowShell={windowAdapter} // optional — absent uses the package's OWN draggable window
66
+ capture={{ uploadFile }} // optional — bind ONE primitive, get the whole upload flow
67
+ pickerOverrides={{ file: FilePickerWindowAdapter }} // optional — the in-package picker is the default
68
+ entityDoors={{ EntityRef, DoorControls, UnresolvedRef }} // optional — hrefFor anchors by default
68
69
  >
69
70
  <PrimaryEntityProvider value={{ type: "organization", id, orgId, label }}>
70
71
  <AssociationCardGrid />
@@ -126,14 +127,51 @@ regenerated by `pnpm gen:entity-types` inside this package. Skew policy:
126
127
  know renders through the fallback (default icon, raw label, no door) instead
127
128
  of crashing.
128
129
 
129
- ## The seven ports
130
+ ## The ports — two required, the rest DEFAULTED
130
131
 
131
- One `AssociationsConfig`, bound once per host. `dataSource` (a structural subset
132
- of `SupabaseClient` answering the demanded RPCs), `identity`, and `errorSink`
133
- are **required** — construction throws without them. `entityOverlay` (icons /
134
- routes / candidate overrides per token), `notifier`, `windowShell`, `capture`,
135
- `pickerOverrides`, and `entityDoors` are optional, each with a defined
136
- degradation never a dead button, never a silent failure.
132
+ One `AssociationsConfig`, bound once per host. Only **`dataSource`** (a
133
+ structural subset of `SupabaseClient` answering the demanded RPCs) and
134
+ **`identity`** are required — construction throws without them, because they
135
+ ARE your app's identity and no package can invent them.
136
+
137
+ Everything else ships a working default (C23 you override ports, you never
138
+ have to fill them):
139
+
140
+ | Port | Default when unbound |
141
+ |---|---|
142
+ | `errorSink` | `createDefaultErrorSink()` — tagged `console.error` per event; announces ONCE that no sink is bound and names the remedy. |
143
+ | `windowShell` | `DefaultWindowShell` — a real draggable / resizable / maximizable / viewport-clamped / portalled / non-modal window; full-width bottom card under 640px. Bind this port only for a real window MANAGER (tray docking, workspace persistence). |
144
+ | `capture` | Bind `uploadFile(file, { folderPath, visibility })` alone and the package ships the file input, drag-and-drop, batching, the stall watchdog and every loud outcome. Bind `requestUpload` instead when you own a richer pipeline. Chips with no path behind them are never rendered. |
145
+ | `pickerOverrides` | The in-package `AssociationPicker` serves every token (candidate list, search, attach/detach, create-then-associate footer). |
146
+ | `entityDoors` | `DoorControlsSlot` renders a real labelled `target="_blank"` anchor from the overlay's `hrefFor`; a token with no route renders nothing rather than a door to nowhere. |
147
+ | `entityOverlay` | Convention columns, default icon, `${label}s`, no door. |
148
+ | `notifier` | Warn-once through the errorSink, and every user-facing failure still reaches the errorSink. (No toast stack ships: a toast surface needs a host-mounted root, so a "default" would be the silent no-op this package exists to prevent.) |
149
+ | `authorDisplay` | `cmt_list`'s denormalized author fields, then "Unknown user" — never blank. |
150
+
151
+ ## Code splitting — the package owns it
152
+
153
+ Import the heavy faces from **`@ai-matrx/associations/react/lazy`** and each one
154
+ arrives in its own chunk on first render. No `next/dynamic`, no `ssr: false`,
155
+ no host glue:
156
+
157
+ ```tsx
158
+ import { AssociationCardGrid } from "@ai-matrx/associations/react/lazy";
159
+ ```
160
+
161
+ For a HOST component you bind to a port, use the same seam:
162
+
163
+ ```tsx
164
+ windowShell: {
165
+ Window: lazyWindowShell(() => import("./hostWindow").then((m) => m.HostWindow)),
166
+ },
167
+ pickerOverrides: {
168
+ file: lazyPickerOverride(() => import("./hostPicker").then((m) => m.HostPicker)),
169
+ },
170
+ ```
171
+
172
+ A per-token picker override should bind `useAssociationPickerBridge(props)` for
173
+ its attach/detach writes — it carries the loud partial-outcome, refusal and
174
+ created-but-unattached reporting so an override never re-implements them.
137
175
 
138
176
  ## Versioning
139
177