@ai-matrx/associations 0.2.0 → 0.3.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/CHANGELOG.md CHANGED
@@ -1,5 +1,100 @@
1
1
  # Changelog — @ai-matrx/associations
2
2
 
3
+ ## 0.3.0 — 2026-08-29 (unreleased; W3 + W4 of the extraction)
4
+
5
+ Waves 3 and 4: the `/react` subpath — the seven hooks riding the /core store
6
+ via `useSyncExternalStore`, and the faces/pickers/capture toolbar. Behavior
7
+ ported VERBATIM from the matrx-frontend originals (re-read in full
8
+ 2026-08-29); only the measured seams inverted onto the W1 ports. `/react` is
9
+ the package's ONE banner group: its dist chunks carry `"use client"` (tsup
10
+ banner + `scripts/stamp-client-boundary.mjs` belt); the root and `/core`
11
+ stay pure, and `verify-tarball` now proves both directions.
12
+
13
+ - **`AssociationsProvider` (`src/react/context.tsx`)** — the one React
14
+ binding point: the host constructs the /core store in its binding module
15
+ and mounts the provider with the five UI ports (notifier / windowShell /
16
+ capture / pickerOverrides / entityDoors). Every hook/face reaches the
17
+ store via `useAssociationsStore()`, which THROWS with a precise message
18
+ when no provider is mounted. `useNotifier()` implements the notifier
19
+ degradation: absent → warn ONCE via errorSink, every user-facing failure
20
+ still screams to the sink.
21
+ - **W3 hooks, signatures byte-compatible with the originals** (the W5 swap
22
+ is a re-export flip + Redux-fragment deletion, call sites unchanged):
23
+ `useAssociations` (+ `useEntityRelationships` alias + re-exported
24
+ `AssociationWriteResult`), `useContainerLinks` (incl. the viewer-aware
25
+ `conversation_files` branch, generation/stale guards verbatim),
26
+ `useEntityTitles` (labels never exempt the liveness read; one settled
27
+ round proves absence), `useCategories`, `useUniversalEntitySearch`
28
+ (debounce + recents via `ues_list`), `useAssociationCandidates`,
29
+ `useAssociationEntitySelectAdapter` (+ the
30
+ `AssociationEntitySelectAdapter` contract, which now lives with the
31
+ hook). Redux selectors/thunks → `useSyncExternalStore` over the store;
32
+ `@/lib/toast` → notifier; `console.error` → errorSink; the host `Json`
33
+ type → `unknown` (`ContainerLink.metadata`, `attach` metadata).
34
+ `useAssociationCandidates`' Redux `selectUserId` read became a LENIENT
35
+ identity-port read (null when unauthenticated): `ownerId` is a
36
+ candidate-read filter, not an auth gate — write paths keep the loud
37
+ `requireUserId` contract.
38
+ - **W4 faces**: `AssociationCard`, `AssociationCardGrid`, `AssociationList`
39
+ (+ `ContainerResourcesAdapter` + `useContainerLinksAdapter`),
40
+ `AssociationPicker` (+ `AssociationCandidateBody` + the
41
+ create-then-associate footer), `UniversalAssociationPicker`,
42
+ `AttachedItemsSheet`, `AssociationCaptureToolbar`, `AssociationWindow`,
43
+ `PrimaryEntityProvider`/`usePrimaryEntity`, plus the content-role chrome
44
+ (`CONTENT_ROLES`/`getContentRoleMeta` — display metadata is /react's;
45
+ the classification stays /core's) and the door-seam helpers
46
+ (`DoorRef`/`UnresolvedRef`).
47
+ - **Seam inversions (deliberate, per the design's Decision 2 table)**:
48
+ - the hardcoded `token === "file"` → FilePickerWindow branch became the
49
+ `pickerOverrides` port: `AssociationPicker` routes a registered token to
50
+ its override component (same `AssociationPickerProps` contract);
51
+ `UniversalAssociationPicker`'s browse mode opens the override too, so
52
+ "file enumeration only happens through the canonical picker" survives
53
+ the inversion; with no override the generic candidate list serves every
54
+ token (the documented degradation);
55
+ - `WindowPanel` → the `windowShell` port; absent → a package-internal
56
+ fixed centered overlay (non-draggable, same anatomy, page behind stays
57
+ interactive);
58
+ - `EntityRef`/`EntityDoorControls`/access-gate `UnresolvedEntityRef` →
59
+ the `entityDoors` port; absent → title text with the overlay `hrefFor`
60
+ anchor when present, plain text otherwise, and the package's plain
61
+ "Unavailable" chip for unreadable targets;
62
+ - the capture toolbar's `features/files` + `features/data-tables` imports
63
+ → the `capture` port, PER-HANDLER optional: chips render only when
64
+ their handler exists, zero handlers → no toolbar and no drop zone
65
+ (never dead buttons, never a drop that swallows files). The original's
66
+ "Add document" picker (a host command palette over
67
+ `listAccessibleDocuments`) has no capture handler by design — hosts
68
+ append it via `extraActions`. "New document" collects its name with an
69
+ inline input row (the original's `TextInputDialog` is host chrome) and
70
+ opens the created document through the registry's `hrefFor` overlay
71
+ instead of a hardcoded `/documents/…` route;
72
+ - lucide icons → the package's own inlined SVGs (C19); host shadcn
73
+ `Input`/`Skeleton`/`Button`/`cn` → `@ai-matrx/design-system` (new
74
+ OPTIONAL peer at the `>=0.2.0` floor, per C18 — never vendored twins).
75
+ - **Store additions (additive, /core)**: `AssociationsStore` now exposes
76
+ the bound `errorSink` and `identity` so /react surfaces scream and read
77
+ identity without a second binding.
78
+ - **W4 gaps (deliberately NOT built — design-system 0.2.0 lacks the
79
+ primitives; vendoring twins is banned)**: `AssociationEntitySelect`,
80
+ `CategorySelect`, `CategoryTagPicker`. They need a Command/combobox
81
+ family (cmdk-style CommandInput/List/Item/Group/Empty + CommandDialog),
82
+ a Select, and the CreatablePicker composite. The
83
+ `AssociationEntitySelectAdapter` contract + default adapter hook (the
84
+ Redux-free seam) shipped anyway, so those faces are a pure-chrome build
85
+ once design-system grows.
86
+ - Tests: 131 (from 97) — jsdom behavioral suites per hook (subscription
87
+ updates, in-flight dedup across two consumers, error surfaces,
88
+ create-then-attach retry + loud outcome) and per face (render +
89
+ interaction against fake ports, every degradation branch: no windowShell,
90
+ no entityDoors, picker override routing, zero capture handlers,
91
+ created-but-unlinked screams).
92
+ - Gate: `pnpm typecheck && pnpm test && pnpm check:package` green; the
93
+ tarball canary installs react + react-dom + `@ai-matrx/design-system`
94
+ (npm, 0.2.0) into the empty project and imports AND requires `/react`,
95
+ proves the `"use client"` banner on both /react chunks and its absence
96
+ on all four pure chunks.
97
+
3
98
  ## 0.2.0 — 2026-08-29 (unreleased; W2 of the extraction)
4
99
 
5
100
  Wave 2: the headless owns-persistence `/core` subpath — the service
package/README.md CHANGED
@@ -4,11 +4,12 @@ THE one way to relate two entities on the AI Matrx platform: association edges,
4
4
  categories, favorites/recents, and the generated entity-type vocabulary — shipped
5
5
  as one package a Matrx client or enterprise app installs.
6
6
 
7
- **v0.2 ships the types root and the headless `/core`** the service
8
- chokepoint, guards, the never-throw result funnel, the registry merge engine,
9
- and the subscribable cache store. The React faces (`/react`: hooks, cards,
10
- list, pickers, capture toolbar) arrive in the next waves. Everything below
11
- about the schema is binding — it is the package's product.
7
+ **v0.3 ships the types root, the headless `/core`, and the React binding
8
+ `/react`** — the service chokepoint, guards, the never-throw result funnel,
9
+ the registry merge engine, the subscribable cache store, the seven hooks, and
10
+ the association faces (cards, list, pickers, attached-items sheet, capture
11
+ toolbar). Everything below about the schema is binding — it is the package's
12
+ product.
12
13
 
13
14
  ```ts
14
15
  import {
@@ -46,6 +47,35 @@ await store.favorites.setFavorite("note", noteId, true);
46
47
  await assertDemandedSchema(supabase, { selfTest: true });
47
48
  ```
48
49
 
50
+ ```tsx
51
+ // /react — the banner'd React binding (peer: react >=18 and
52
+ // @ai-matrx/design-system >=0.2.0 for the faces' chrome). Bind ONCE:
53
+ import {
54
+ AssociationsProvider,
55
+ useAssociations, // the seven hooks keep the app originals' exact signatures
56
+ AssociationCardGrid, // + AssociationCard / AssociationList / pickers / capture toolbar
57
+ PrimaryEntityProvider,
58
+ } from "@ai-matrx/associations/react";
59
+
60
+ <AssociationsProvider
61
+ store={store} // the /core store from createAssociationsStore
62
+ notifier={toastAdapter} // optional — absent warns once, failures still hit errorSink
63
+ windowShell={windowAdapter} // optional — absent falls back to a fixed overlay
64
+ capture={captureHandlers} // optional, per-handler — chips render only when bound
65
+ pickerOverrides={{ file: FilePickerWindowAdapter }} // optional per-token pickers
66
+ entityDoors={{ EntityRef, DoorControls, UnresolvedRef }} // optional doors
67
+ >
68
+ <PrimaryEntityProvider value={{ type: "organization", id, orgId, label }}>
69
+ <AssociationCardGrid />
70
+ </PrimaryEntityProvider>
71
+ </AssociationsProvider>;
72
+ ```
73
+
74
+ The faces style with Tailwind semantic tokens (`bg-card`,
75
+ `text-muted-foreground`, …): register this package as a Tailwind v4 source in
76
+ the consuming app (`@source "../node_modules/@ai-matrx/associations/dist"`)
77
+ the same way `@ai-matrx/design-system` documents.
78
+
49
79
  ## The demanded schema — read this first
50
80
 
51
81
  This package is **opinionated like the Python `matrx-*` packages**: it does not
@@ -3019,7 +3019,9 @@ function createAssociationsStore(config) {
3019
3019
  helpers,
3020
3020
  registry,
3021
3021
  services: { associations, categories },
3022
- registerEntityOverlay: registry.registerEntityOverlay
3022
+ registerEntityOverlay: registry.registerEntityOverlay,
3023
+ errorSink,
3024
+ identity: config.identity
3023
3025
  };
3024
3026
  }
3025
3027