@ai-matrx/associations 0.7.3 → 0.7.5

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.
@@ -2132,6 +2132,15 @@ function EntityCandidateList({
2132
2132
  enabled,
2133
2133
  ...search.trim() ? { search: search.trim() } : {}
2134
2134
  });
2135
+ const { results: recentCandidates, loading: recentsLoading } = useUniversalEntitySearch({
2136
+ query: "",
2137
+ tokens: [token],
2138
+ enabled: enabled && !search.trim()
2139
+ });
2140
+ const visibleRecents = search.trim() ? [] : recentCandidates;
2141
+ const recentIds = new Set(visibleRecents.map((candidate) => candidate.id));
2142
+ const ordinaryCandidates = search.trim() ? candidates : candidates.filter((candidate) => !recentIds.has(candidate.id));
2143
+ const visibleCount = visibleRecents.length + ordinaryCandidates.length;
2135
2144
  const canCreate = info.titleColumn !== null && info.listCandidates === null;
2136
2145
  const toggle = async (id, title) => {
2137
2146
  if (busyId) return;
@@ -2162,7 +2171,7 @@ function EntityCandidateList({
2162
2171
  }
2163
2172
  )
2164
2173
  ] }),
2165
- /* @__PURE__ */ jsx6("div", { className: "flex-1 min-h-0 overflow-y-auto -mx-1 px-1", children: loading && candidates.length === 0 ? /* @__PURE__ */ jsxs4(ListMessage, { children: [
2174
+ /* @__PURE__ */ jsx6("div", { className: "flex-1 min-h-0 overflow-y-auto -mx-1 px-1", children: (loading || recentsLoading) && visibleCount === 0 ? /* @__PURE__ */ jsxs4(ListMessage, { children: [
2166
2175
  /* @__PURE__ */ jsx6(SpinnerIcon, { className: "h-4 w-4 animate-spin" }),
2167
2176
  "Loading\u2026"
2168
2177
  ] }) : error ? /* @__PURE__ */ jsxs4("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-[12px] text-destructive", children: [
@@ -2177,23 +2186,69 @@ function EntityCandidateList({
2177
2186
  children: "Retry"
2178
2187
  }
2179
2188
  )
2180
- ] }) : candidates.length === 0 ? /* @__PURE__ */ jsx6(ListMessage, { children: canCreate ? `Nothing to attach yet \u2014 create a new ${info.label.toLowerCase()} below.` : "Nothing to attach." }) : /* @__PURE__ */ jsx6("ul", { className: "space-y-0.5", children: candidates.map((c) => {
2181
- const attached = attachedIds.has(c.id);
2182
- const busy = busyId === c.id;
2189
+ ] }) : visibleCount === 0 ? /* @__PURE__ */ jsx6(ListMessage, { children: canCreate ? `Nothing to attach yet \u2014 create a new ${info.label.toLowerCase()} below.` : "Nothing to attach." }) : /* @__PURE__ */ jsxs4("div", { className: "space-y-2", children: [
2190
+ visibleRecents.length > 0 && /* @__PURE__ */ jsx6(
2191
+ CandidateSection,
2192
+ {
2193
+ label: "Recent",
2194
+ candidates: visibleRecents,
2195
+ attachedIds,
2196
+ busyId,
2197
+ icon: /* @__PURE__ */ jsx6(Icon, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
2198
+ onToggle: toggle
2199
+ }
2200
+ ),
2201
+ ordinaryCandidates.length > 0 && /* @__PURE__ */ jsx6(
2202
+ CandidateSection,
2203
+ {
2204
+ label: visibleRecents.length > 0 ? `All ${info.labelPlural}` : null,
2205
+ candidates: ordinaryCandidates,
2206
+ attachedIds,
2207
+ busyId,
2208
+ icon: /* @__PURE__ */ jsx6(Icon, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
2209
+ onToggle: toggle
2210
+ }
2211
+ )
2212
+ ] }) }),
2213
+ canCreate && /* @__PURE__ */ jsx6(
2214
+ CreateAndAttachFooter,
2215
+ {
2216
+ token,
2217
+ orgId,
2218
+ seed: search.trim(),
2219
+ onAttach,
2220
+ onCreated: reload
2221
+ }
2222
+ )
2223
+ ] });
2224
+ }
2225
+ function CandidateSection({
2226
+ label,
2227
+ candidates,
2228
+ attachedIds,
2229
+ busyId,
2230
+ icon,
2231
+ onToggle
2232
+ }) {
2233
+ return /* @__PURE__ */ jsxs4("section", { className: "space-y-0.5", children: [
2234
+ label && /* @__PURE__ */ jsx6("p", { className: "px-1 text-[10px] font-medium uppercase tracking-wider text-muted-foreground/60", children: label }),
2235
+ /* @__PURE__ */ jsx6("ul", { className: "space-y-0.5", children: candidates.map((candidate) => {
2236
+ const attached = attachedIds.has(candidate.id);
2237
+ const busy = busyId === candidate.id;
2183
2238
  return /* @__PURE__ */ jsx6("li", { children: /* @__PURE__ */ jsxs4(
2184
2239
  "button",
2185
2240
  {
2186
2241
  type: "button",
2187
2242
  disabled: busy,
2188
- onClick: () => toggle(c.id, c.title),
2243
+ onClick: () => void onToggle(candidate.id, candidate.title),
2189
2244
  className: cn2(
2190
2245
  "group flex min-h-11 w-full items-center gap-2 rounded-md px-2.5 py-2 text-left text-sm transition-colors md:min-h-0",
2191
2246
  "hover:bg-accent disabled:opacity-50",
2192
2247
  attached && "bg-accent/40"
2193
2248
  ),
2194
2249
  children: [
2195
- /* @__PURE__ */ jsx6(Icon, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
2196
- /* @__PURE__ */ jsx6("span", { className: "flex-1 min-w-0 truncate text-foreground", children: c.title }),
2250
+ icon,
2251
+ /* @__PURE__ */ jsx6("span", { className: "flex-1 min-w-0 truncate text-foreground", children: candidate.title }),
2197
2252
  /* @__PURE__ */ jsx6(
2198
2253
  "span",
2199
2254
  {
@@ -2206,18 +2261,8 @@ function EntityCandidateList({
2206
2261
  )
2207
2262
  ]
2208
2263
  }
2209
- ) }, c.id);
2210
- }) }) }),
2211
- canCreate && /* @__PURE__ */ jsx6(
2212
- CreateAndAttachFooter,
2213
- {
2214
- token,
2215
- orgId,
2216
- seed: search.trim(),
2217
- onAttach,
2218
- onCreated: reload
2219
- }
2220
- )
2264
+ ) }, candidate.id);
2265
+ }) })
2221
2266
  ] });
2222
2267
  }
2223
2268
  function CreateAndAttachFooter({
@@ -2487,27 +2532,35 @@ function UniversalAssociationPicker(props) {
2487
2532
  ),
2488
2533
  loading && browseToken === null && /* @__PURE__ */ jsx8(SpinnerIcon, { className: "absolute right-2.5 top-1/2 h-4 w-4 -translate-y-1/2 animate-spin text-muted-foreground" })
2489
2534
  ] }),
2490
- /* @__PURE__ */ jsx8("div", { className: "flex flex-wrap gap-1", children: tokens.map((t) => {
2491
- const info = store.registry.getEntityInfo(t);
2492
- const ChipIcon = info.Icon ?? DefaultEntityIcon;
2493
- const active = browseToken === t;
2494
- return /* @__PURE__ */ jsxs6(
2495
- "button",
2496
- {
2497
- type: "button",
2498
- onClick: () => setBrowseToken(active ? null : t),
2499
- className: cn4(
2500
- "inline-flex items-center gap-1 rounded-md border px-1.5 py-0.5 text-[11px] transition-colors",
2501
- active ? "border-primary/50 bg-primary/10 text-foreground" : "border-border text-muted-foreground hover:text-foreground hover:bg-accent"
2502
- ),
2503
- children: [
2504
- /* @__PURE__ */ jsx8(ChipIcon, { className: "h-3 w-3" }),
2505
- info.labelPlural
2506
- ]
2507
- },
2508
- t
2509
- );
2510
- }) }),
2535
+ /* @__PURE__ */ jsx8(
2536
+ "div",
2537
+ {
2538
+ role: "group",
2539
+ "aria-label": "Entity type filters",
2540
+ className: "flex shrink-0 flex-nowrap gap-1 overflow-x-auto pb-1",
2541
+ children: tokens.map((t) => {
2542
+ const info = store.registry.getEntityInfo(t);
2543
+ const ChipIcon = info.Icon ?? DefaultEntityIcon;
2544
+ const active = browseToken === t;
2545
+ return /* @__PURE__ */ jsxs6(
2546
+ "button",
2547
+ {
2548
+ type: "button",
2549
+ onClick: () => setBrowseToken(active ? null : t),
2550
+ className: cn4(
2551
+ "inline-flex shrink-0 items-center gap-1 rounded-md border px-1.5 py-0.5 text-[11px] transition-colors",
2552
+ active ? "border-primary/50 bg-primary/10 text-foreground" : "border-border text-muted-foreground hover:text-foreground hover:bg-accent"
2553
+ ),
2554
+ children: [
2555
+ /* @__PURE__ */ jsx8(ChipIcon, { className: "h-3 w-3" }),
2556
+ info.labelPlural
2557
+ ]
2558
+ },
2559
+ t
2560
+ );
2561
+ })
2562
+ }
2563
+ ),
2511
2564
  browseToken && BrowseOverride ? (
2512
2565
  // The token's canonical host picker (e.g. the stored-files browser)
2513
2566
  // owns per-token browsing when registered — never a plain list twin.
@@ -2540,71 +2593,79 @@ function UniversalAssociationPicker(props) {
2540
2593
  onAttach: (id, title) => onAttach(browseToken, id, title),
2541
2594
  onDetach: (id) => onDetach(browseToken, id)
2542
2595
  }
2543
- ) }) : /* @__PURE__ */ jsx8("div", { className: "-mx-1 min-h-0 flex-1 overflow-y-auto px-1", children: results.length === 0 ? /* @__PURE__ */ jsx8("p", { className: "py-6 text-center text-[12px] text-muted-foreground", children: loading ? "Searching\u2026" : query.trim() ? "No matches \u2014 narrow with a type chip to browse." : "No recent items. Type to search, or pick a type to browse." }) : /* @__PURE__ */ jsxs6("div", { className: "space-y-2", children: [
2544
- isRecents && /* @__PURE__ */ jsx8("p", { className: "px-1 text-[10px] font-medium uppercase tracking-wider text-muted-foreground/60", children: "Recent" }),
2545
- [...groups.entries()].map(([token, items]) => {
2546
- const info = store.registry.getEntityInfo(token);
2547
- const GroupIcon = info.Icon ?? DefaultEntityIcon;
2548
- return /* @__PURE__ */ jsxs6("div", { className: "space-y-0.5", children: [
2549
- /* @__PURE__ */ jsxs6("p", { className: "flex items-center gap-1 px-1 text-[10px] font-medium uppercase tracking-wider text-muted-foreground/70", children: [
2550
- /* @__PURE__ */ jsx8(GroupIcon, { className: "h-3 w-3" }),
2551
- info.labelPlural
2552
- ] }),
2553
- /* @__PURE__ */ jsx8("ul", { className: "space-y-0.5", children: items.map((c) => {
2554
- const key = attachedKey(c.token, c.id);
2555
- const attached = attachedKeys.has(key);
2556
- const busy = busyKey === key;
2557
- return /* @__PURE__ */ jsxs6(
2558
- "li",
2559
- {
2560
- className: cn4(
2561
- "group/entity-ref group flex items-center gap-1 rounded-md pr-1.5 transition-colors",
2562
- "hover:bg-accent",
2563
- attached && "bg-accent/40"
2564
- ),
2565
- children: [
2566
- /* @__PURE__ */ jsxs6(
2567
- "button",
2568
- {
2569
- type: "button",
2570
- disabled: busy,
2571
- onClick: () => toggle(c),
2572
- title: attached ? `Detach "${c.title}"` : `Attach "${c.title}"`,
2573
- className: cn4(
2574
- "flex min-w-0 flex-1 items-center gap-2 rounded-md px-2.5 py-1.5 text-left text-sm",
2575
- "disabled:opacity-50"
2596
+ ) }) : /* @__PURE__ */ jsx8(
2597
+ "div",
2598
+ {
2599
+ role: "region",
2600
+ "aria-label": "Association search results",
2601
+ className: "-mx-1 min-h-24 flex-1 overflow-y-auto overscroll-contain px-1",
2602
+ children: results.length === 0 ? /* @__PURE__ */ jsx8("p", { className: "py-6 text-center text-[12px] text-muted-foreground", children: loading ? "Searching\u2026" : query.trim() ? "No matches \u2014 narrow with a type chip to browse." : "No recent items. Type to search, or pick a type to browse." }) : /* @__PURE__ */ jsxs6("div", { className: "space-y-2", children: [
2603
+ isRecents && /* @__PURE__ */ jsx8("p", { className: "px-1 text-[10px] font-medium uppercase tracking-wider text-muted-foreground/60", children: "Recent" }),
2604
+ [...groups.entries()].map(([token, items]) => {
2605
+ const info = store.registry.getEntityInfo(token);
2606
+ const GroupIcon = info.Icon ?? DefaultEntityIcon;
2607
+ return /* @__PURE__ */ jsxs6("div", { className: "space-y-0.5", children: [
2608
+ /* @__PURE__ */ jsxs6("p", { className: "flex items-center gap-1 px-1 text-[10px] font-medium uppercase tracking-wider text-muted-foreground/70", children: [
2609
+ /* @__PURE__ */ jsx8(GroupIcon, { className: "h-3 w-3" }),
2610
+ info.labelPlural
2611
+ ] }),
2612
+ /* @__PURE__ */ jsx8("ul", { className: "space-y-0.5", children: items.map((c) => {
2613
+ const key = attachedKey(c.token, c.id);
2614
+ const attached = attachedKeys.has(key);
2615
+ const busy = busyKey === key;
2616
+ return /* @__PURE__ */ jsxs6(
2617
+ "li",
2618
+ {
2619
+ className: cn4(
2620
+ "group/entity-ref group flex items-center gap-1 rounded-md pr-1.5 transition-colors",
2621
+ "hover:bg-accent",
2622
+ attached && "bg-accent/40"
2623
+ ),
2624
+ children: [
2625
+ /* @__PURE__ */ jsxs6(
2626
+ "button",
2627
+ {
2628
+ type: "button",
2629
+ disabled: busy,
2630
+ onClick: () => toggle(c),
2631
+ title: attached ? `Detach "${c.title}"` : `Attach "${c.title}"`,
2632
+ className: cn4(
2633
+ "flex min-w-0 flex-1 items-center gap-2 rounded-md px-2.5 py-1.5 text-left text-sm",
2634
+ "disabled:opacity-50"
2635
+ ),
2636
+ children: [
2637
+ /* @__PURE__ */ jsx8("span", { className: "min-w-0 flex-1 truncate text-foreground", children: c.title }),
2638
+ /* @__PURE__ */ jsx8(
2639
+ "span",
2640
+ {
2641
+ className: cn4(
2642
+ "flex h-5 w-5 shrink-0 items-center justify-center rounded-full",
2643
+ attached ? "bg-primary text-primary-foreground" : "border border-border text-muted-foreground group-hover:border-primary/60"
2644
+ ),
2645
+ children: busy ? /* @__PURE__ */ jsx8(SpinnerIcon, { className: "h-3 w-3 animate-spin" }) : attached ? /* @__PURE__ */ jsx8(CheckIcon, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx8(PlusIcon, { className: "h-3 w-3" })
2646
+ }
2647
+ )
2648
+ ]
2649
+ }
2576
2650
  ),
2577
- children: [
2578
- /* @__PURE__ */ jsx8("span", { className: "min-w-0 flex-1 truncate text-foreground", children: c.title }),
2579
- /* @__PURE__ */ jsx8(
2580
- "span",
2581
- {
2582
- className: cn4(
2583
- "flex h-5 w-5 shrink-0 items-center justify-center rounded-full",
2584
- attached ? "bg-primary text-primary-foreground" : "border border-border text-muted-foreground group-hover:border-primary/60"
2585
- ),
2586
- children: busy ? /* @__PURE__ */ jsx8(SpinnerIcon, { className: "h-3 w-3 animate-spin" }) : attached ? /* @__PURE__ */ jsx8(CheckIcon, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx8(PlusIcon, { className: "h-3 w-3" })
2587
- }
2588
- )
2589
- ]
2590
- }
2591
- ),
2592
- /* @__PURE__ */ jsx8(
2593
- DoorControlsSlot,
2594
- {
2595
- token: c.token,
2596
- id: c.id,
2597
- name: c.title
2598
- }
2599
- )
2600
- ]
2601
- },
2602
- key
2603
- );
2604
- }) })
2605
- ] }, token);
2606
- })
2607
- ] }) })
2651
+ /* @__PURE__ */ jsx8(
2652
+ DoorControlsSlot,
2653
+ {
2654
+ token: c.token,
2655
+ id: c.id,
2656
+ name: c.title
2657
+ }
2658
+ )
2659
+ ]
2660
+ },
2661
+ key
2662
+ );
2663
+ }) })
2664
+ ] }, token);
2665
+ })
2666
+ ] })
2667
+ }
2668
+ )
2608
2669
  ] });
2609
2670
  }
2610
2671