@ai-matrx/associations 0.6.2 → 0.7.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 +15 -0
- package/README.md +13 -7
- package/dist/{CommentThread-CqZHMTsH.d.cts → CommentThread-HW4hLdsd.d.cts} +12 -2
- package/dist/{CommentThread-CqZHMTsH.d.ts → CommentThread-HW4hLdsd.d.ts} +12 -2
- package/dist/react/index.cjs +129 -15
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +2 -2
- package/dist/react/index.d.ts +2 -2
- package/dist/react/index.js +129 -15
- package/dist/react/index.js.map +1 -1
- package/dist/react/lazy/index.d.cts +1 -1
- package/dist/react/lazy/index.d.ts +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog — @ai-matrx/associations
|
|
2
2
|
|
|
3
|
+
## 0.7.0 — 2026-08-31 (role-safe pin and detach)
|
|
4
|
+
|
|
5
|
+
- `useContainerLinks` now exposes `setPinned`, which updates the exact
|
|
6
|
+
role-qualified edge, preserves every sibling metadata field, and refreshes
|
|
7
|
+
both endpoint caches through the canonical `assoc_add` chokepoint.
|
|
8
|
+
- `AssociationCard` attached-item windows now expose pin/unpin and pass the
|
|
9
|
+
edge role through both pin and detach. Previously, a role-bearing row was
|
|
10
|
+
visible but its detach control targeted only the role-less edge and could
|
|
11
|
+
silently leave the displayed row attached.
|
|
12
|
+
- The default `AssociationList` adapter now derives pin state from edge
|
|
13
|
+
metadata and supplies the same role-safe pin operation. Failed pin writes
|
|
14
|
+
announce themselves through the notifier instead of disappearing.
|
|
15
|
+
- Hook and face regressions prove metadata preservation and exact-role routing
|
|
16
|
+
for both pin and detach. Full package suite: 189 tests.
|
|
17
|
+
|
|
3
18
|
## 0.6.2 — 2026-08-31 (vocabulary catch-up: commerce_certified_printer)
|
|
4
19
|
|
|
5
20
|
Regenerated `src/entity-types.generated.ts` from live `platform.entity_types`:
|
package/README.md
CHANGED
|
@@ -4,16 +4,16 @@ 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
|
-
|
|
8
|
-
`/react
|
|
9
|
-
|
|
10
|
-
the association faces (cards, list,
|
|
11
|
-
toolbar). Everything below about the
|
|
12
|
-
product.
|
|
7
|
+
The package ships the types root, the headless `/core`, the React binding
|
|
8
|
+
`/react`, and the package-owned lazy React entry `/react/lazy`: the service
|
|
9
|
+
chokepoint, guards, never-throw result funnel, registry merge engine,
|
|
10
|
+
subscribable cache store, hooks, and the association faces (cards, list,
|
|
11
|
+
pickers, attached-items sheet, capture toolbar). Everything below about the
|
|
12
|
+
schema is binding — it is the package's product.
|
|
13
13
|
|
|
14
14
|
```ts
|
|
15
15
|
import {
|
|
16
|
-
type EntityTypeToken, //
|
|
16
|
+
type EntityTypeToken, // generated from platform.entity_types
|
|
17
17
|
ENTITY_TYPE_METADATA, // per-token schema/table/label/titleColumn/contentRole
|
|
18
18
|
isEntityTypeToken, // runtime guard, narrows to the union
|
|
19
19
|
type AssociationEdge, // one assoc_for_entity row (both directions)
|
|
@@ -48,6 +48,12 @@ await store.favorites.setFavorite("note", noteId, true);
|
|
|
48
48
|
await assertDemandedSchema(supabase, { selfTest: true });
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
+
`useContainerLinks({ containerType, containerId, orgId })` is the canonical
|
|
52
|
+
container-side React API. Its `attach`, `detach`, and `setPinned` operations all
|
|
53
|
+
target the exact role-qualified edge; `setPinned` preserves the edge's other
|
|
54
|
+
metadata fields. `AssociationCardGrid` and the default `AssociationList`
|
|
55
|
+
adapter expose those same operations without host wiring.
|
|
56
|
+
|
|
51
57
|
```tsx
|
|
52
58
|
// /react — the banner'd React binding (peer: react >=18;
|
|
53
59
|
// @ai-matrx/design-system is a real dependency — the faces' chrome
|
|
@@ -1483,6 +1483,11 @@ interface UseContainerLinksReturn {
|
|
|
1483
1483
|
}) => Promise<AssociationWriteResult>;
|
|
1484
1484
|
/** Detach a resource from this container. */
|
|
1485
1485
|
detach: (token: EntityTypeToken, resourceId: string, role?: string) => Promise<AssociationWriteResult>;
|
|
1486
|
+
/**
|
|
1487
|
+
* Toggle the edge's agent-context pin while preserving every other metadata
|
|
1488
|
+
* field and the exact semantic role.
|
|
1489
|
+
*/
|
|
1490
|
+
setPinned: (token: EntityTypeToken, resourceId: string, pinned: boolean, role?: string) => Promise<AssociationWriteResult>;
|
|
1486
1491
|
}
|
|
1487
1492
|
declare function useContainerLinks(args: UseContainerLinksArgs): UseContainerLinksReturn;
|
|
1488
1493
|
|
|
@@ -1604,7 +1609,12 @@ interface AttachedItemsSheetProps {
|
|
|
1604
1609
|
links: ContainerLink[];
|
|
1605
1610
|
/** Opens the attach picker. Omitted when the token cannot list candidates. */
|
|
1606
1611
|
onAdd?: () => void;
|
|
1607
|
-
onDetach: (resourceId: string) => Promise<{
|
|
1612
|
+
onDetach: (resourceId: string, role?: string | null) => Promise<{
|
|
1613
|
+
ok: boolean;
|
|
1614
|
+
error?: string;
|
|
1615
|
+
}>;
|
|
1616
|
+
/** Toggle metadata.pinned on this exact role-qualified edge. */
|
|
1617
|
+
onSetPinned?: (resourceId: string, pinned: boolean, role?: string | null) => Promise<{
|
|
1608
1618
|
ok: boolean;
|
|
1609
1619
|
error?: string;
|
|
1610
1620
|
}>;
|
|
@@ -1675,7 +1685,7 @@ interface ContainerResourcesAdapter {
|
|
|
1675
1685
|
* Optional pin capability (edge `metadata.pinned`) — pinned resources stay
|
|
1676
1686
|
* inline in agent context at every tier. Rows show a pin toggle when set.
|
|
1677
1687
|
*/
|
|
1678
|
-
setPinned?: (token: EntityTypeToken, resourceId: string, pinned: boolean) => Promise<{
|
|
1688
|
+
setPinned?: (token: EntityTypeToken, resourceId: string, pinned: boolean, role?: string) => Promise<{
|
|
1679
1689
|
ok: boolean;
|
|
1680
1690
|
error?: string;
|
|
1681
1691
|
}>;
|
|
@@ -1483,6 +1483,11 @@ interface UseContainerLinksReturn {
|
|
|
1483
1483
|
}) => Promise<AssociationWriteResult>;
|
|
1484
1484
|
/** Detach a resource from this container. */
|
|
1485
1485
|
detach: (token: EntityTypeToken, resourceId: string, role?: string) => Promise<AssociationWriteResult>;
|
|
1486
|
+
/**
|
|
1487
|
+
* Toggle the edge's agent-context pin while preserving every other metadata
|
|
1488
|
+
* field and the exact semantic role.
|
|
1489
|
+
*/
|
|
1490
|
+
setPinned: (token: EntityTypeToken, resourceId: string, pinned: boolean, role?: string) => Promise<AssociationWriteResult>;
|
|
1486
1491
|
}
|
|
1487
1492
|
declare function useContainerLinks(args: UseContainerLinksArgs): UseContainerLinksReturn;
|
|
1488
1493
|
|
|
@@ -1604,7 +1609,12 @@ interface AttachedItemsSheetProps {
|
|
|
1604
1609
|
links: ContainerLink[];
|
|
1605
1610
|
/** Opens the attach picker. Omitted when the token cannot list candidates. */
|
|
1606
1611
|
onAdd?: () => void;
|
|
1607
|
-
onDetach: (resourceId: string) => Promise<{
|
|
1612
|
+
onDetach: (resourceId: string, role?: string | null) => Promise<{
|
|
1613
|
+
ok: boolean;
|
|
1614
|
+
error?: string;
|
|
1615
|
+
}>;
|
|
1616
|
+
/** Toggle metadata.pinned on this exact role-qualified edge. */
|
|
1617
|
+
onSetPinned?: (resourceId: string, pinned: boolean, role?: string | null) => Promise<{
|
|
1608
1618
|
ok: boolean;
|
|
1609
1619
|
error?: string;
|
|
1610
1620
|
}>;
|
|
@@ -1675,7 +1685,7 @@ interface ContainerResourcesAdapter {
|
|
|
1675
1685
|
* Optional pin capability (edge `metadata.pinned`) — pinned resources stay
|
|
1676
1686
|
* inline in agent context at every tier. Rows show a pin toggle when set.
|
|
1677
1687
|
*/
|
|
1678
|
-
setPinned?: (token: EntityTypeToken, resourceId: string, pinned: boolean) => Promise<{
|
|
1688
|
+
setPinned?: (token: EntityTypeToken, resourceId: string, pinned: boolean, role?: string) => Promise<{
|
|
1679
1689
|
ok: boolean;
|
|
1680
1690
|
error?: string;
|
|
1681
1691
|
}>;
|
package/dist/react/index.cjs
CHANGED
|
@@ -1109,6 +1109,12 @@ var useEntityRelationships = useAssociations;
|
|
|
1109
1109
|
|
|
1110
1110
|
// src/react/hooks/useContainerLinks.ts
|
|
1111
1111
|
var import_react3 = require("react");
|
|
1112
|
+
function metadataRecord(metadata) {
|
|
1113
|
+
if (metadata === null || typeof metadata !== "object" || Array.isArray(metadata)) {
|
|
1114
|
+
return metadata == null ? {} : null;
|
|
1115
|
+
}
|
|
1116
|
+
return Object.fromEntries(Object.entries(metadata));
|
|
1117
|
+
}
|
|
1112
1118
|
function useContainerLinks(args) {
|
|
1113
1119
|
const { containerType, containerId, orgId } = args;
|
|
1114
1120
|
const store = useAssociationsStore();
|
|
@@ -1230,6 +1236,31 @@ function useContainerLinks(args) {
|
|
|
1230
1236
|
}
|
|
1231
1237
|
return result;
|
|
1232
1238
|
};
|
|
1239
|
+
const setPinned = async (token, resourceId, pinned, role) => {
|
|
1240
|
+
if (!containerId) return { ok: false, error: "Missing container id" };
|
|
1241
|
+
const exactRole = role ?? null;
|
|
1242
|
+
const link = incoming.find(
|
|
1243
|
+
(candidate) => candidate.token === token && candidate.resourceId === resourceId && candidate.role === exactRole
|
|
1244
|
+
);
|
|
1245
|
+
if (!link) return { ok: false, error: "Association edge not found" };
|
|
1246
|
+
const currentMetadata = metadataRecord(link.metadata);
|
|
1247
|
+
if (!currentMetadata) {
|
|
1248
|
+
return {
|
|
1249
|
+
ok: false,
|
|
1250
|
+
error: "Association metadata is not an object and cannot be updated safely"
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
return store.add({
|
|
1254
|
+
sourceType: token,
|
|
1255
|
+
sourceId: resourceId,
|
|
1256
|
+
targetType: containerType,
|
|
1257
|
+
targetId: containerId,
|
|
1258
|
+
...orgId != null ? { orgId } : {},
|
|
1259
|
+
...link.label != null ? { label: link.label } : {},
|
|
1260
|
+
...role !== void 0 ? { role } : {},
|
|
1261
|
+
metadata: { ...currentMetadata, pinned }
|
|
1262
|
+
});
|
|
1263
|
+
};
|
|
1233
1264
|
const reload = async () => {
|
|
1234
1265
|
await Promise.all([
|
|
1235
1266
|
reloadGeneric(),
|
|
@@ -1247,7 +1278,8 @@ function useContainerLinks(args) {
|
|
|
1247
1278
|
attachedIdsFor,
|
|
1248
1279
|
linksFor,
|
|
1249
1280
|
attach,
|
|
1250
|
-
detach
|
|
1281
|
+
detach,
|
|
1282
|
+
setPinned
|
|
1251
1283
|
};
|
|
1252
1284
|
}
|
|
1253
1285
|
|
|
@@ -2628,6 +2660,9 @@ function UniversalAssociationPicker(props) {
|
|
|
2628
2660
|
var import_react15 = require("react");
|
|
2629
2661
|
var import_design_system5 = require("@ai-matrx/design-system");
|
|
2630
2662
|
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
2663
|
+
function metadataIsPinned(metadata) {
|
|
2664
|
+
return metadata !== null && typeof metadata === "object" && !Array.isArray(metadata) && Reflect.get(metadata, "pinned") === true;
|
|
2665
|
+
}
|
|
2631
2666
|
function AttachedItemsSheet(props) {
|
|
2632
2667
|
const store = useAssociationsStore();
|
|
2633
2668
|
const info = store.registry.getEntityInfo(props.token);
|
|
@@ -2663,7 +2698,8 @@ function AttachedItemsSheet(props) {
|
|
|
2663
2698
|
enabled: props.open,
|
|
2664
2699
|
links: props.links,
|
|
2665
2700
|
...props.onAdd ? { onAdd: props.onAdd } : {},
|
|
2666
|
-
onDetach: props.onDetach
|
|
2701
|
+
onDetach: props.onDetach,
|
|
2702
|
+
...props.onSetPinned ? { onSetPinned: props.onSetPinned } : {}
|
|
2667
2703
|
}
|
|
2668
2704
|
)
|
|
2669
2705
|
}
|
|
@@ -2674,7 +2710,8 @@ function AttachedItemsBody({
|
|
|
2674
2710
|
enabled,
|
|
2675
2711
|
links,
|
|
2676
2712
|
onAdd,
|
|
2677
|
-
onDetach
|
|
2713
|
+
onDetach,
|
|
2714
|
+
onSetPinned
|
|
2678
2715
|
}) {
|
|
2679
2716
|
const store = useAssociationsStore();
|
|
2680
2717
|
const notifier = useNotifier();
|
|
@@ -2716,11 +2753,12 @@ function AttachedItemsBody({
|
|
|
2716
2753
|
cancelled = true;
|
|
2717
2754
|
};
|
|
2718
2755
|
}, [token, idKey, enabled, store]);
|
|
2719
|
-
const detach = async (
|
|
2756
|
+
const detach = async (link) => {
|
|
2757
|
+
const resourceId = link.resourceId;
|
|
2720
2758
|
if (busyId) return;
|
|
2721
2759
|
setBusyId(resourceId);
|
|
2722
2760
|
try {
|
|
2723
|
-
const res = await onDetach(resourceId);
|
|
2761
|
+
const res = await onDetach(resourceId, link.role);
|
|
2724
2762
|
if (!res.ok) {
|
|
2725
2763
|
notifier.error(`Couldn't detach${res.error ? `: ${res.error}` : ""}`);
|
|
2726
2764
|
}
|
|
@@ -2728,6 +2766,20 @@ function AttachedItemsBody({
|
|
|
2728
2766
|
setBusyId(null);
|
|
2729
2767
|
}
|
|
2730
2768
|
};
|
|
2769
|
+
const setPinned = async (link, pinned) => {
|
|
2770
|
+
if (!onSetPinned || busyId) return;
|
|
2771
|
+
setBusyId(link.resourceId);
|
|
2772
|
+
try {
|
|
2773
|
+
const res = await onSetPinned(link.resourceId, pinned, link.role);
|
|
2774
|
+
if (!res.ok) {
|
|
2775
|
+
notifier.error(
|
|
2776
|
+
`Couldn't ${pinned ? "pin" : "unpin"}${res.error ? `: ${res.error}` : ""}`
|
|
2777
|
+
);
|
|
2778
|
+
}
|
|
2779
|
+
} finally {
|
|
2780
|
+
setBusyId(null);
|
|
2781
|
+
}
|
|
2782
|
+
};
|
|
2731
2783
|
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
|
|
2732
2784
|
titleError ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "mb-2 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-[12px] text-destructive", children: [
|
|
2733
2785
|
"Showing names recorded at attach time \u2014 they may be out of date.",
|
|
@@ -2742,6 +2794,7 @@ function AttachedItemsBody({
|
|
|
2742
2794
|
const missing = !titles.has(link.resourceId);
|
|
2743
2795
|
const name = resolved ?? link.label ?? (missing ? null : "Untitled");
|
|
2744
2796
|
const busy = busyId === link.resourceId;
|
|
2797
|
+
const pinned = metadataIsPinned(link.metadata);
|
|
2745
2798
|
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
2746
2799
|
"li",
|
|
2747
2800
|
{
|
|
@@ -2761,12 +2814,35 @@ function AttachedItemsBody({
|
|
|
2761
2814
|
className: "text-foreground"
|
|
2762
2815
|
}
|
|
2763
2816
|
) }),
|
|
2817
|
+
onSetPinned ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2818
|
+
"button",
|
|
2819
|
+
{
|
|
2820
|
+
type: "button",
|
|
2821
|
+
disabled: busy,
|
|
2822
|
+
onClick: () => void setPinned(link, !pinned),
|
|
2823
|
+
title: pinned ? "Unpin (stops staying in agent context)" : "Pin \u2014 always in agent context",
|
|
2824
|
+
"aria-label": pinned ? "Unpin" : "Pin",
|
|
2825
|
+
className: (0, import_design_system5.cn)(
|
|
2826
|
+
"flex h-6 w-6 shrink-0 items-center justify-center rounded-md transition-colors hover:bg-accent hover:text-foreground disabled:opacity-50",
|
|
2827
|
+
pinned ? "text-primary" : "text-muted-foreground"
|
|
2828
|
+
),
|
|
2829
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2830
|
+
PinIcon,
|
|
2831
|
+
{
|
|
2832
|
+
className: (0, import_design_system5.cn)(
|
|
2833
|
+
"h-3.5 w-3.5",
|
|
2834
|
+
pinned && "fill-primary/20"
|
|
2835
|
+
)
|
|
2836
|
+
}
|
|
2837
|
+
)
|
|
2838
|
+
}
|
|
2839
|
+
) : null,
|
|
2764
2840
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2765
2841
|
"button",
|
|
2766
2842
|
{
|
|
2767
2843
|
type: "button",
|
|
2768
2844
|
disabled: busy,
|
|
2769
|
-
onClick: () => void detach(link
|
|
2845
|
+
onClick: () => void detach(link),
|
|
2770
2846
|
title: "Detach",
|
|
2771
2847
|
className: (0, import_design_system5.cn)(
|
|
2772
2848
|
"flex h-6 w-6 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors",
|
|
@@ -2862,7 +2938,15 @@ function AssociationCard({
|
|
|
2862
2938
|
const role = getContentRoleMeta(info.contentRole);
|
|
2863
2939
|
const Icon = info.Icon ?? DefaultEntityIcon;
|
|
2864
2940
|
const [listOpen, setListOpen] = (0, import_react16.useState)(false);
|
|
2865
|
-
const {
|
|
2941
|
+
const {
|
|
2942
|
+
status,
|
|
2943
|
+
countFor,
|
|
2944
|
+
attachedIdsFor,
|
|
2945
|
+
linksFor,
|
|
2946
|
+
attach,
|
|
2947
|
+
detach,
|
|
2948
|
+
setPinned
|
|
2949
|
+
} = useContainerLinks({
|
|
2866
2950
|
containerType: container?.type ?? "organization",
|
|
2867
2951
|
containerId: container?.id ?? null,
|
|
2868
2952
|
orgId: container?.orgId ?? null
|
|
@@ -2959,7 +3043,8 @@ function AssociationCard({
|
|
|
2959
3043
|
setOpen(true);
|
|
2960
3044
|
}
|
|
2961
3045
|
} : {},
|
|
2962
|
-
onDetach: (resourceId) => detach(token, resourceId)
|
|
3046
|
+
onDetach: (resourceId, role2) => detach(token, resourceId, role2 ?? void 0),
|
|
3047
|
+
onSetPinned: (resourceId, pinned, role2) => setPinned(token, resourceId, pinned, role2 ?? void 0)
|
|
2963
3048
|
}
|
|
2964
3049
|
),
|
|
2965
3050
|
canAttach && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
@@ -3018,6 +3103,9 @@ function AssociationCardGrid({
|
|
|
3018
3103
|
var import_react17 = require("react");
|
|
3019
3104
|
var import_design_system8 = require("@ai-matrx/design-system");
|
|
3020
3105
|
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
3106
|
+
function metadataIsPinned2(metadata) {
|
|
3107
|
+
return metadata !== null && typeof metadata === "object" && !Array.isArray(metadata) && Reflect.get(metadata, "pinned") === true;
|
|
3108
|
+
}
|
|
3021
3109
|
function useContainerLinksAdapter(container, tokens) {
|
|
3022
3110
|
const store = useAssociationsStore();
|
|
3023
3111
|
const links = useContainerLinks({
|
|
@@ -3032,6 +3120,7 @@ function useContainerLinksAdapter(container, tokens) {
|
|
|
3032
3120
|
resourceId: l.resourceId,
|
|
3033
3121
|
label: l.label,
|
|
3034
3122
|
role: l.role,
|
|
3123
|
+
pinned: metadataIsPinned2(l.metadata),
|
|
3035
3124
|
removable: true
|
|
3036
3125
|
}))
|
|
3037
3126
|
) : [];
|
|
@@ -3041,11 +3130,13 @@ function useContainerLinksAdapter(container, tokens) {
|
|
|
3041
3130
|
reload: links.reload,
|
|
3042
3131
|
rows,
|
|
3043
3132
|
attach: async (token, id, title) => links.attach(token, id, title),
|
|
3044
|
-
detach: async (token, id, role) => links.detach(token, id, role)
|
|
3133
|
+
detach: async (token, id, role) => links.detach(token, id, role),
|
|
3134
|
+
setPinned: async (token, id, pinned, role) => links.setPinned(token, id, pinned, role)
|
|
3045
3135
|
};
|
|
3046
3136
|
}
|
|
3047
3137
|
function AssociationList(props) {
|
|
3048
3138
|
const store = useAssociationsStore();
|
|
3139
|
+
const notifier = useNotifier();
|
|
3049
3140
|
const ctxEntity = usePrimaryEntity();
|
|
3050
3141
|
const container = props.container ?? ctxEntity ?? null;
|
|
3051
3142
|
const defaultAdapter = useContainerLinksAdapter(
|
|
@@ -3058,6 +3149,7 @@ function AssociationList(props) {
|
|
|
3058
3149
|
const [pickerToken, setPickerToken] = (0, import_react17.useState)(null);
|
|
3059
3150
|
const [showUniversal, setShowUniversal] = (0, import_react17.useState)(false);
|
|
3060
3151
|
const [removingKeys, setRemovingKeys] = (0, import_react17.useState)(/* @__PURE__ */ new Set());
|
|
3152
|
+
const [pinningKeys, setPinningKeys] = (0, import_react17.useState)(/* @__PURE__ */ new Set());
|
|
3061
3153
|
const rows = tokenFilter ? adapter.rows.filter((r) => tokenFilter.includes(r.token)) : adapter.rows;
|
|
3062
3154
|
const visibleRows = rows.filter((r) => !removingKeys.has(r.key));
|
|
3063
3155
|
const { titleFor } = useEntityTitles(
|
|
@@ -3081,6 +3173,31 @@ function AssociationList(props) {
|
|
|
3081
3173
|
});
|
|
3082
3174
|
return res;
|
|
3083
3175
|
};
|
|
3176
|
+
const handlePin = async (row, pinned) => {
|
|
3177
|
+
if (!adapter.setPinned) return;
|
|
3178
|
+
const info = store.registry.tryGetEntityInfo(row.token);
|
|
3179
|
+
if (!info) return;
|
|
3180
|
+
setPinningKeys((prev) => new Set(prev).add(row.key));
|
|
3181
|
+
try {
|
|
3182
|
+
const res = await adapter.setPinned(
|
|
3183
|
+
info.token,
|
|
3184
|
+
row.resourceId,
|
|
3185
|
+
pinned,
|
|
3186
|
+
row.role ?? void 0
|
|
3187
|
+
);
|
|
3188
|
+
if (!res.ok) {
|
|
3189
|
+
notifier.error(
|
|
3190
|
+
`Couldn't ${pinned ? "pin" : "unpin"} resource${res.error ? `: ${res.error}` : ""}`
|
|
3191
|
+
);
|
|
3192
|
+
}
|
|
3193
|
+
} finally {
|
|
3194
|
+
setPinningKeys((prev) => {
|
|
3195
|
+
const next = new Set(prev);
|
|
3196
|
+
next.delete(row.key);
|
|
3197
|
+
return next;
|
|
3198
|
+
});
|
|
3199
|
+
}
|
|
3200
|
+
};
|
|
3084
3201
|
const openRow = (row) => {
|
|
3085
3202
|
if (props.openEntity) {
|
|
3086
3203
|
props.openEntity(row.token, row.resourceId);
|
|
@@ -3271,18 +3388,15 @@ function AssociationList(props) {
|
|
|
3271
3388
|
"button",
|
|
3272
3389
|
{
|
|
3273
3390
|
type: "button",
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
row.resourceId,
|
|
3277
|
-
!(row.pinned ?? false)
|
|
3278
|
-
),
|
|
3391
|
+
disabled: pinningKeys.has(row.key),
|
|
3392
|
+
onClick: () => void handlePin(row, !(row.pinned ?? false)),
|
|
3279
3393
|
title: row.pinned ? "Unpin (stops staying in agent context)" : "Pin \u2014 always in agent context",
|
|
3280
3394
|
"aria-label": row.pinned ? "Unpin" : "Pin",
|
|
3281
3395
|
className: (0, import_design_system8.cn)(
|
|
3282
3396
|
"shrink-0 rounded p-0.5 transition-colors hover:!text-foreground",
|
|
3283
3397
|
row.pinned ? "text-primary" : "text-muted-foreground/0 group-hover:text-muted-foreground"
|
|
3284
3398
|
),
|
|
3285
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
3399
|
+
children: pinningKeys.has(row.key) ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SpinnerIcon, { className: "h-3 w-3 animate-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
3286
3400
|
PinIcon,
|
|
3287
3401
|
{
|
|
3288
3402
|
className: (0, import_design_system8.cn)(
|