@ai-matrx/associations 0.14.79 → 0.14.80

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
@@ -2,6 +2,7 @@
2
2
 
3
3
  ### Unreleased
4
4
 
5
+ - **A signed-out tab is told it is signed out (2026-10-19).** `mapPgError` used to read a 42501 `permission denied for function` as "Permission denied"; an anonymous caller (an ended or expired session) meets exactly that on every demanded door, so the remedy it named was wrong. Now PGRST301 / PGRST303 / 28000, and 42501 "permission denied for function" while the session is not attached, map to `unauthorized` with `SIGNED_OUT_MESSAGE` ("You're signed out. Sign in again — your text is kept here."). With the session attached a 42501 stays `forbidden_org` (a real grant gap). New optional ports: `identity.sessionAttached()` and the React `signIn` port (CommentThread shows a Sign in button beside a signed-out post or load failure). Guard: `src/core/__tests__/rpcResult.test.ts`. Consumer action: bind `identity.sessionAttached` (matrx-frontend: `sessionStateNow() === "attached"`) and `signIn` on `AssociationsProvider` (open login in a new tab); without `sessionAttached` every such 42501 reads as signed out.
5
6
  - **The attach pickers put the cursor in their search box on open.** `UniversalAssociationPicker` (the `+ Add` panel of `AssociationList`) and the per-type `AssociationPicker` search inputs now `autoFocus`. Guard: `src/react/__tests__/faces.test.tsx` ("puts the cursor in the search box when it opens"). Consumer action: none.
6
7
  - **Shared code ships once per side (2026-10-18).** The root and `/core` were each built `splitting: false`, so each inlined the 420 KB entity-type vocabulary (`src/entity-types.generated.ts`) plus three more modules: a host importing both paid twice. ESM now builds root + `/core` in one split pass (`chunk-server-*.js`, no banner) and `/react` + `/react/lazy` in another (stamped). CJS unsplit; declarations unchanged. ESM JS 1,177,448 -> 756,462 bytes; root + `/core` closure 947 KB -> 520 KB; every entry's export names identical (ESM and CJS). Guard: `../scripts/check-ships-once.mjs`, run in `build`. Consumer action: none.
7
8
  - **`DefaultWindowShell` carries no drag code of its own.** Header drag and grip resize run on design-system's `useDragMove` (painted per frame, one commit per gesture, cursor held, raised on press) and its clamp is `clampRectToViewport` (48px grab strip, header never above the top). The port contract is unchanged; hosts with a window manager still bind `windowShell`. Guard: `src/react/__tests__/default-window-shell-drag.test.tsx`. Consumer action: none.
@@ -9,11 +10,20 @@
9
10
  - **`AssociationList` takes `title`.** Default stays "Resources"; pass a string to rename the heading, or `null` when the host card already titles the list (the attach controls stay, right-aligned). Guard: `src/react/__tests__/association-list-title.test.tsx`. Consumer action: replace any host CSS that hides the list's `h3` (matrx-frontend: `HIDE_LIST_HEADING` in `BrandResearchCard.tsx`, `ResearchTopicLinks.tsx`) with `title={null}`.
10
11
  - **Linking records no longer freezes a name onto the edge.** `useContainerLinksAdapter().attach` (and `AssociationCard`'s picker) used to store the picked record's title as the association label, so a research topic linked to brand "AI Matrx" listed on that brand as "AI Matrx". The picked title is no longer stored; each side shows the linked record's own current name from the title resolver. A deliberate label is still stored via `useContainerLinks().attach(token, id, label)`. Guard: `src/react/__tests__/outgoing-links.test.tsx` ("attach never freezes a name"). Consumer action: remove any adapter wrapper that blanks `label` (matrx-frontend: `useNeutralLinksAdapter`) and use `useContainerLinksAdapter` directly; edges already written with a label keep it until cleared.
11
12
 
12
- ## 0.14.79
13
+ ## 0.14.80
13
14
 
14
15
  UNVERIFIED: this release has no Unreleased entry and the source comparison could not be made (the caller supplied no source comparison). Whether source changed is unknown; consumer action is unknown.
15
16
  Check the package's gitHead on npm against this version's gitHead: `git diff <previous gitHead> <this gitHead> -- apps/shared/associations/src`.
16
17
 
18
+ ## 0.14.79
19
+
20
+ SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
21
+ so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
22
+ Diff: `git diff d116ad2ab5 77f29a65b9 -- apps/shared/associations/src`
23
+
24
+ Changed shipped source files (1):
25
+ - `apps/shared/associations/src/entity-types.generated.ts`
26
+
17
27
  ## 0.14.78
18
28
 
19
29
  SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
@@ -53,6 +53,13 @@ interface AssociationsIdentity {
53
53
  requireUserId(): string;
54
54
  /** Org for created rows/edges (the CategoryTagPicker create path). */
55
55
  ensureOrgId?(): Promise<string>;
56
+ /**
57
+ * Whether the client's database session is attached right now. A 42501
58
+ * "permission denied for function" from a demanded door means the request
59
+ * went out anonymous (an ended or expired session) — UNLESS the session is
60
+ * attached, when it is a real grant gap. Absent → treated as not attached.
61
+ */
62
+ sessionAttached?(): boolean;
56
63
  }
57
64
  /**
58
65
  * The scream seam. Every degraded path, every `demanded_schema_violation`,
@@ -53,6 +53,13 @@ interface AssociationsIdentity {
53
53
  requireUserId(): string;
54
54
  /** Org for created rows/edges (the CategoryTagPicker create path). */
55
55
  ensureOrgId?(): Promise<string>;
56
+ /**
57
+ * Whether the client's database session is attached right now. A 42501
58
+ * "permission denied for function" from a demanded door means the request
59
+ * went out anonymous (an ended or expired session) — UNLESS the session is
60
+ * attached, when it is a real grant gap. Absent → treated as not attached.
61
+ */
62
+ sessionAttached?(): boolean;
56
63
  }
57
64
  /**
58
65
  * The scream seam. Every degraded path, every `demanded_schema_violation`,
@@ -2674,7 +2674,9 @@ function readPgFields(e) {
2674
2674
  }
2675
2675
  return { pgCode: null, pgMessage: null, pgHint: void 0 };
2676
2676
  }
2677
- function createRpcResultHelpers(errorSink) {
2677
+ var SIGNED_OUT_LEAD = "You're signed out";
2678
+ var SIGNED_OUT_MESSAGE = `${SIGNED_OUT_LEAD}. Sign in again \u2014 your text is kept here.`;
2679
+ function createRpcResultHelpers(errorSink, sessionAttached) {
2678
2680
  function mapPgError(e) {
2679
2681
  const { pgCode, pgMessage, pgHint } = readPgFields(e);
2680
2682
  if (pgCode === "PGRST202") {
@@ -2707,10 +2709,13 @@ function createRpcResultHelpers(errorSink) {
2707
2709
  }
2708
2710
  if (pgCode === "PGRST116")
2709
2711
  return { code: "not_found", message: "Not found" };
2710
- if (pgCode === "42501")
2712
+ if (pgCode === "PGRST301" || pgCode === "PGRST303" || pgCode === "28000")
2713
+ return { code: "unauthorized", message: SIGNED_OUT_MESSAGE };
2714
+ if (pgCode === "42501") {
2715
+ const anonymous = /permission denied for function/i.test(pgMessage ?? "") && !(sessionAttached?.() ?? false);
2716
+ if (anonymous) return { code: "unauthorized", message: SIGNED_OUT_MESSAGE };
2711
2717
  return { code: "forbidden_org", message: "Permission denied" };
2712
- if (pgCode === "PGRST301" || pgCode === "PGRST303")
2713
- return { code: "unauthorized", message: "Your session expired" };
2718
+ }
2714
2719
  if (pgCode === "57014") {
2715
2720
  const out2 = {
2716
2721
  code: "internal",
@@ -4359,7 +4364,10 @@ function createAssociationsStore(config) {
4359
4364
  identity: config.identity,
4360
4365
  errorSink,
4361
4366
  guards: createAssociationGuards(errorSink),
4362
- rpc: createRpcResultHelpers(errorSink),
4367
+ rpc: createRpcResultHelpers(
4368
+ errorSink,
4369
+ config.identity.sessionAttached ? () => config.identity.sessionAttached() : void 0
4370
+ ),
4363
4371
  registry
4364
4372
  };
4365
4373
  const associations = createAssociationsService(deps);