@ai-matrx/associations 0.2.0 → 0.4.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.
@@ -0,0 +1,3926 @@
1
+ "use client";
2
+
3
+ // src/react/context.tsx
4
+ import { createContext, useContext, useRef } from "react";
5
+ import { jsx } from "react/jsx-runtime";
6
+ var AssociationsReactContext = createContext(
7
+ null
8
+ );
9
+ function AssociationsProvider({
10
+ store,
11
+ notifier,
12
+ windowShell,
13
+ capture,
14
+ pickerOverrides,
15
+ entityDoors,
16
+ children
17
+ }) {
18
+ const notifierWarned = useRef(false);
19
+ return /* @__PURE__ */ jsx(
20
+ AssociationsReactContext.Provider,
21
+ {
22
+ value: {
23
+ store,
24
+ notifierWarned,
25
+ ...notifier !== void 0 ? { notifier } : {},
26
+ ...windowShell !== void 0 ? { windowShell } : {},
27
+ ...capture !== void 0 ? { capture } : {},
28
+ ...pickerOverrides !== void 0 ? { pickerOverrides } : {},
29
+ ...entityDoors !== void 0 ? { entityDoors } : {}
30
+ },
31
+ children
32
+ }
33
+ );
34
+ }
35
+ function requireBinding() {
36
+ const binding = useContext(AssociationsReactContext);
37
+ if (!binding) {
38
+ throw new Error(
39
+ "@ai-matrx/associations/react: no <AssociationsProvider> above this component. Construct the store once with createAssociationsStore(cfg) in your host binding and mount <AssociationsProvider store={store}> at the top of the tree."
40
+ );
41
+ }
42
+ return binding;
43
+ }
44
+ function useAssociationsStore() {
45
+ return requireBinding().store;
46
+ }
47
+ function useAssociationsUiPorts() {
48
+ const { store: _store, notifierWarned: _warned, ...ports } = requireBinding();
49
+ return ports;
50
+ }
51
+ function useNotifier() {
52
+ const binding = requireBinding();
53
+ if (binding.notifier) return binding.notifier;
54
+ const { store, notifierWarned } = binding;
55
+ const warnOnce = () => {
56
+ if (notifierWarned.current) return;
57
+ notifierWarned.current = true;
58
+ store.errorSink({
59
+ code: "notifier_missing",
60
+ message: "@ai-matrx/associations/react: no notifier port bound \u2014 user-facing failures are routed to the errorSink only. Bind `notifier` on AssociationsProvider for toasts."
61
+ });
62
+ };
63
+ return {
64
+ success() {
65
+ warnOnce();
66
+ },
67
+ error(msg, opts) {
68
+ warnOnce();
69
+ store.errorSink({
70
+ code: "user_facing_error",
71
+ message: msg,
72
+ ...opts?.description !== void 0 ? { context: { description: opts.description } } : {}
73
+ });
74
+ }
75
+ };
76
+ }
77
+
78
+ // src/react/hooks/useAssociations.ts
79
+ import { useEffect, useRef as useRef2, useSyncExternalStore } from "react";
80
+
81
+ // src/results.ts
82
+ function isAssociationsRpcErr(r) {
83
+ return r.ok === false;
84
+ }
85
+
86
+ // src/entity-types.generated.ts
87
+ var ENTITY_TYPE_TOKENS = [
88
+ "access_request",
89
+ "activity",
90
+ "agent",
91
+ "agent_card",
92
+ "agent_definition_version",
93
+ "agent_drift_alert",
94
+ "agent_exemplar",
95
+ "agent_mandate_note",
96
+ "agent_prompt_remediation",
97
+ "agent_run",
98
+ "agent_run_stage",
99
+ "agent_shortcut",
100
+ "agent_surface_binding",
101
+ "agent_template",
102
+ "agent_usage",
103
+ "ai_api",
104
+ "ai_endpoint",
105
+ "ai_model",
106
+ "ai_model_alias",
107
+ "ai_offering",
108
+ "ai_provider",
109
+ "ai_setting",
110
+ "app",
111
+ "app_definition_version",
112
+ "app_error",
113
+ "app_execution",
114
+ "app_instance",
115
+ "app_log",
116
+ "app_rate_limit",
117
+ "app_setting",
118
+ "app_sync_status",
119
+ "approach",
120
+ "artifact",
121
+ "assessment",
122
+ "assessment_item",
123
+ "assessment_result",
124
+ "assist",
125
+ "batch_cost_event",
126
+ "batch_provider_batch",
127
+ "batch_work_item",
128
+ "browser_account_binding",
129
+ "browser_action_event",
130
+ "browser_authenticator_window",
131
+ "browser_capture",
132
+ "browser_control_request",
133
+ "browser_handoff",
134
+ "browser_login_attempt",
135
+ "browser_login_recipe",
136
+ "browser_profile",
137
+ "browser_profile_checkpoint",
138
+ "browser_run",
139
+ "browser_site_observation",
140
+ "browser_site_policy",
141
+ "browser_stream_ticket",
142
+ "canvas_comment",
143
+ "canvas_comment_like",
144
+ "canvas_item",
145
+ "canvas_item_state",
146
+ "canvas_like",
147
+ "canvas_score",
148
+ "canvas_view",
149
+ "category",
150
+ "cmp_entry",
151
+ "cmp_feedback",
152
+ "code_file",
153
+ "code_folder",
154
+ "code_repository",
155
+ "coding_session",
156
+ "coding_session_entry",
157
+ "comment",
158
+ "commerce_asset_allocation",
159
+ "commerce_asset_grading",
160
+ "commerce_asset_identifier",
161
+ "commerce_asset_lot_event",
162
+ "commerce_asset_mandate_result",
163
+ "commerce_asset_price_factor",
164
+ "commerce_asset_reshoot_request",
165
+ "commerce_asset_review",
166
+ "commerce_asset_unknown",
167
+ "commerce_cloud_sync_connection",
168
+ "commerce_ebay_business_policy",
169
+ "commerce_ebay_category",
170
+ "commerce_ebay_category_aspect",
171
+ "commerce_ebay_category_tree",
172
+ "commerce_ebay_custom_policy",
173
+ "commerce_ebay_inventory_item",
174
+ "commerce_ebay_inventory_item_group",
175
+ "commerce_ebay_inventory_item_group_member",
176
+ "commerce_ebay_inventory_location",
177
+ "commerce_ebay_listing",
178
+ "commerce_ebay_marketplace_policy",
179
+ "commerce_ebay_media_asset",
180
+ "commerce_ebay_notification_destination",
181
+ "commerce_ebay_notification_event",
182
+ "commerce_ebay_notification_subscription",
183
+ "commerce_ebay_notification_topic",
184
+ "commerce_ebay_offer",
185
+ "commerce_ebay_order",
186
+ "commerce_ebay_order_line_item",
187
+ "commerce_ebay_shipping_fulfillment",
188
+ "commerce_ebay_store_category",
189
+ "commerce_human_correction",
190
+ "commerce_intake_artifact",
191
+ "commerce_intake_asset",
192
+ "commerce_intake_batch",
193
+ "commerce_label_batch",
194
+ "commerce_label_code",
195
+ "commerce_marketplace_account",
196
+ "commerce_marketplace_account_deletion_audit",
197
+ "commerce_marketplace_api_call",
198
+ "commerce_marketplace_rate_budget",
199
+ "commerce_marketplace_site",
200
+ "commerce_marketplace_sync_run",
201
+ "commerce_prediction_outcome",
202
+ "commerce_product",
203
+ "commerce_product_channel_ref",
204
+ "commerce_product_media",
205
+ "commerce_product_variant",
206
+ "commerce_recall_audit",
207
+ "comparison_set",
208
+ "contact_medium",
209
+ "contact_submission",
210
+ "content_ir_kind",
211
+ "content_ir_kind_component",
212
+ "content_ir_kind_component_incident",
213
+ "content_ir_kind_conformance",
214
+ "content_ir_kind_edge",
215
+ "content_ir_kind_example",
216
+ "content_ir_kind_instance",
217
+ "content_ir_kind_surface",
218
+ "context_item",
219
+ "context_item_suggestion",
220
+ "conversation",
221
+ "conversation_value",
222
+ "crm_address",
223
+ "crm_affiliation",
224
+ "crm_blocklist_entry",
225
+ "crm_contact_candidate",
226
+ "crm_deal",
227
+ "crm_deal_stage_event",
228
+ "crm_enrichment_call",
229
+ "crm_interaction",
230
+ "crm_merge_candidate",
231
+ "crm_outreach_list",
232
+ "crm_outreach_list_member",
233
+ "crm_party_merge",
234
+ "crm_registry_ingest_run",
235
+ "crm_registry_source",
236
+ "crm_saved_view",
237
+ "crm_sending_event",
238
+ "crm_sending_identity",
239
+ "crm_sending_identity_check",
240
+ "crm_sending_policy",
241
+ "custom_entity_definition",
242
+ "custom_field_definition",
243
+ "custom_field_target",
244
+ "custom_record",
245
+ "cx_agent_memory",
246
+ "cx_agent_plan",
247
+ "cx_agent_task",
248
+ "cx_code_edit",
249
+ "cx_code_message_file",
250
+ "cx_media",
251
+ "cx_observational_memory",
252
+ "cx_observational_memory_event",
253
+ "cx_pending_injection",
254
+ "cx_request",
255
+ "cx_request_snapshot",
256
+ "cx_tool_trace",
257
+ "cx_user_request",
258
+ "cx_user_todo",
259
+ "data_store",
260
+ "dataset",
261
+ "derive_run",
262
+ "dict_setting",
263
+ "dm_conversation",
264
+ "dm_message",
265
+ "dm_participant",
266
+ "domain_classification",
267
+ "esign_campaign",
268
+ "esign_campaign_member",
269
+ "esign_consent_disclosure",
270
+ "esign_envelope",
271
+ "esign_envelope_certificate",
272
+ "esign_envelope_document",
273
+ "esign_envelope_event",
274
+ "esign_envelope_external_ref",
275
+ "esign_envelope_signer",
276
+ "esign_provider",
277
+ "esign_provider_binding",
278
+ "esign_signing_key",
279
+ "fc_card",
280
+ "fc_detail",
281
+ "fc_set",
282
+ "feature_doc",
283
+ "file",
284
+ "file_analysis",
285
+ "file_entities",
286
+ "file_overrides",
287
+ "file_page_annotations",
288
+ "file_pages",
289
+ "file_version",
290
+ "flexible_data",
291
+ "folder",
292
+ "game_badge",
293
+ "game_result",
294
+ "game_room",
295
+ "global_execution",
296
+ "global_execution_checkpoint",
297
+ "global_execution_event",
298
+ "global_meter_entry",
299
+ "global_origin",
300
+ "global_request",
301
+ "growth_loop_event",
302
+ "growth_loop_run",
303
+ "growth_loop_stage_run",
304
+ "guided_checklist_run",
305
+ "heatmap_save",
306
+ "hindsight_enrollment",
307
+ "hindsight_finding",
308
+ "hindsight_regression_case",
309
+ "hindsight_replay",
310
+ "hindsight_replay_step",
311
+ "hindsight_review",
312
+ "hr_access_audit",
313
+ "hr_access_role",
314
+ "hr_accommodation_request",
315
+ "hr_ai_evidence",
316
+ "hr_alert_routing_rule",
317
+ "hr_application",
318
+ "hr_approval_authority",
319
+ "hr_approval_delegation",
320
+ "hr_asset",
321
+ "hr_asset_assignment",
322
+ "hr_attendance_exception",
323
+ "hr_auto_close_rule",
324
+ "hr_availability",
325
+ "hr_background_check",
326
+ "hr_benefits_event",
327
+ "hr_calculation_snapshot",
328
+ "hr_candidate",
329
+ "hr_candidate_conversion",
330
+ "hr_candidate_message",
331
+ "hr_careers_portal",
332
+ "hr_checklist_item",
333
+ "hr_checklist_run",
334
+ "hr_checklist_template",
335
+ "hr_checklist_template_item",
336
+ "hr_compensation",
337
+ "hr_corrective_action",
338
+ "hr_course",
339
+ "hr_course_version",
340
+ "hr_credential",
341
+ "hr_crew",
342
+ "hr_deduction_code",
343
+ "hr_department",
344
+ "hr_derived_grant",
345
+ "hr_disposition_event",
346
+ "hr_earning_code",
347
+ "hr_eeo_response",
348
+ "hr_emergency_contact",
349
+ "hr_employee",
350
+ "hr_employee_private",
351
+ "hr_employer_profile",
352
+ "hr_employment",
353
+ "hr_employment_pin",
354
+ "hr_engagement",
355
+ "hr_establishment",
356
+ "hr_external_identity",
357
+ "hr_field_policy",
358
+ "hr_holiday",
359
+ "hr_holiday_calendar",
360
+ "hr_i9",
361
+ "hr_i9_document",
362
+ "hr_incident",
363
+ "hr_incident_party",
364
+ "hr_interview",
365
+ "hr_interview_kit",
366
+ "hr_job_title",
367
+ "hr_jurisdiction",
368
+ "hr_jurisdiction_rule",
369
+ "hr_jurisdiction_rule_class",
370
+ "hr_jurisdiction_rule_org_decision",
371
+ "hr_jurisdiction_rule_test",
372
+ "hr_kiosk_device",
373
+ "hr_kiosk_session",
374
+ "hr_labor_target",
375
+ "hr_leave_case",
376
+ "hr_leave_enrollment",
377
+ "hr_leave_ledger",
378
+ "hr_leave_policy",
379
+ "hr_leave_request",
380
+ "hr_legal_hold",
381
+ "hr_legal_hold_item",
382
+ "hr_location",
383
+ "hr_new_hire_report",
384
+ "hr_offer",
385
+ "hr_opening",
386
+ "hr_overtime_alert",
387
+ "hr_overtime_alert_rule",
388
+ "hr_overtime_preapproval",
389
+ "hr_pay_group",
390
+ "hr_pay_period",
391
+ "hr_pay_period_employment",
392
+ "hr_payroll_export",
393
+ "hr_payroll_export_line",
394
+ "hr_position_assignment",
395
+ "hr_posting",
396
+ "hr_posting_publication",
397
+ "hr_provider_binding",
398
+ "hr_provider_event",
399
+ "hr_provisioning_result",
400
+ "hr_punch",
401
+ "hr_recalculation_batch",
402
+ "hr_record_class",
403
+ "hr_records_request",
404
+ "hr_reference_check",
405
+ "hr_reporting_line",
406
+ "hr_requisition",
407
+ "hr_restricted_note",
408
+ "hr_retention_rule",
409
+ "hr_role_assignment",
410
+ "hr_schedule",
411
+ "hr_schedule_change",
412
+ "hr_schedule_guidance",
413
+ "hr_schedule_template",
414
+ "hr_schedule_template_shift",
415
+ "hr_scorecard",
416
+ "hr_separation",
417
+ "hr_shift",
418
+ "hr_shift_claim",
419
+ "hr_staffing_requirement",
420
+ "hr_survey",
421
+ "hr_survey_invitation",
422
+ "hr_survey_question",
423
+ "hr_survey_response",
424
+ "hr_tax_registration",
425
+ "hr_tax_withholding",
426
+ "hr_time_adjustment",
427
+ "hr_training_assignment",
428
+ "hr_training_attempt",
429
+ "hr_transcript_entry",
430
+ "hr_verification_letter_request",
431
+ "hr_work_interval",
432
+ "hr_workflow_binding",
433
+ "hr_workflow_decision",
434
+ "hr_workflow_definition",
435
+ "hr_workflow_event",
436
+ "hr_workflow_failure",
437
+ "hr_workflow_flow_type",
438
+ "hr_workflow_instance",
439
+ "hr_workflow_step",
440
+ "hr_workflow_step_definition",
441
+ "hr_workweek",
442
+ "iam_api_key",
443
+ "industry_curator",
444
+ "interview_document_revision",
445
+ "interview_hole",
446
+ "interview_question",
447
+ "interview_session",
448
+ "interview_turn",
449
+ "invitation",
450
+ "invitation_code",
451
+ "invitation_request",
452
+ "item_mastery",
453
+ "judge_verdict",
454
+ "kg_alert",
455
+ "kg_suggestion_ack",
456
+ "kg_sweep_queue",
457
+ "kg_sweep_run",
458
+ "kg_sweep_state",
459
+ "kg_value_match",
460
+ "league_membership",
461
+ "learn_doc",
462
+ "library_doc",
463
+ "mandate",
464
+ "mandate_binding",
465
+ "mandate_treatment",
466
+ "marketing_initiative",
467
+ "masterwork_corpus_item",
468
+ "masterwork_run",
469
+ "membership",
470
+ "message",
471
+ "message_template",
472
+ "ner_shadow",
473
+ "note",
474
+ "note_folder",
475
+ "notification",
476
+ "notification_channel_preference",
477
+ "notification_event_override",
478
+ "notification_event_type",
479
+ "notification_preference",
480
+ "ops_issue_event",
481
+ "ops_proof_check",
482
+ "ops_proof_run",
483
+ "ops_proof_scenario",
484
+ "organization",
485
+ "output_feedback",
486
+ "page_extraction_job",
487
+ "page_extraction_page_run",
488
+ "party",
489
+ "party_contact_point",
490
+ "pc_article",
491
+ "pc_episode",
492
+ "pc_show",
493
+ "pc_studio_run",
494
+ "pc_studio_run_asset",
495
+ "pdf_redaction_audit",
496
+ "plan_cms_fill_item",
497
+ "plan_cms_fill_job",
498
+ "plan_entity",
499
+ "plan_node",
500
+ "plan_node_artifact",
501
+ "plan_node_step",
502
+ "plan_profile",
503
+ "platform_actor_session",
504
+ "platform_actor_token",
505
+ "platform_actor_token_event",
506
+ "platform_continued_access",
507
+ "platform_outcome_event",
508
+ "platform_outsider_consumer",
509
+ "platform_saved_view",
510
+ "podcast_race",
511
+ "processed_document",
512
+ "processed_document_page",
513
+ "product_capture_file",
514
+ "product_capture_item",
515
+ "product_capture_payload",
516
+ "product_capture_product",
517
+ "product_capture_question",
518
+ "project",
519
+ "provision",
520
+ "purpose",
521
+ "quiz_session",
522
+ "rag_ingest_run",
523
+ "redaction_mapping",
524
+ "research_analysis",
525
+ "research_content",
526
+ "research_context_bundle",
527
+ "research_document",
528
+ "research_keyword",
529
+ "research_media",
530
+ "research_source",
531
+ "research_synthesis",
532
+ "research_tag",
533
+ "research_template",
534
+ "research_topic",
535
+ "route_manifest_entry",
536
+ "rulebook",
537
+ "sandbox_instance",
538
+ "sch_agent_task",
539
+ "sch_run",
540
+ "sch_task",
541
+ "sch_trigger",
542
+ "scope",
543
+ "scope_association_suggestion",
544
+ "scope_item_value_suggestion",
545
+ "scope_suggestion",
546
+ "scope_type",
547
+ "seo_ai_visibility_citation",
548
+ "seo_ai_visibility_claim",
549
+ "seo_ai_visibility_panel",
550
+ "seo_ai_visibility_response",
551
+ "seo_ai_visibility_signal",
552
+ "seo_backlink",
553
+ "seo_backlink_change_event",
554
+ "seo_backlink_dimension_snapshot",
555
+ "seo_backlink_observation",
556
+ "seo_backlink_snapshot",
557
+ "seo_change_assessment",
558
+ "seo_change_event",
559
+ "seo_change_item",
560
+ "seo_change_metric",
561
+ "seo_change_set",
562
+ "seo_change_theory",
563
+ "seo_collection_run",
564
+ "seo_competitor",
565
+ "seo_competitor_observation",
566
+ "seo_competitor_opportunity",
567
+ "seo_coverage_mention",
568
+ "seo_coverage_tracker",
569
+ "seo_dimension_value_matcher",
570
+ "seo_engine_schedule",
571
+ "seo_geo_place",
572
+ "seo_gsc_dig_rule",
573
+ "seo_keyword",
574
+ "seo_keyword_class_rule",
575
+ "seo_keyword_edge",
576
+ "seo_keyword_facet",
577
+ "seo_keyword_market",
578
+ "seo_keyword_market_observation",
579
+ "seo_keyword_place",
580
+ "seo_keyword_saved_view",
581
+ "seo_keyword_topic",
582
+ "seo_landscape_brief",
583
+ "seo_link_gap_domain",
584
+ "seo_link_gap_match",
585
+ "seo_page_measurement_health",
586
+ "seo_page_performance",
587
+ "seo_provider_call",
588
+ "seo_provider_task",
589
+ "seo_rank_observation",
590
+ "seo_rank_target",
591
+ "seo_raw_payload",
592
+ "seo_referring_domain_profile",
593
+ "seo_reputation_case",
594
+ "seo_search_performance_daily",
595
+ "seo_serp_mention",
596
+ "seo_serp_opportunity",
597
+ "seo_serp_result",
598
+ "seo_serp_snapshot",
599
+ "seo_site_geo_area",
600
+ "seo_site_keyword_offering",
601
+ "seo_site_keyword_value",
602
+ "seo_site_offering_value",
603
+ "seo_site_topic_value",
604
+ "seo_site_value_combo",
605
+ "seo_site_value_worth",
606
+ "seo_site_vocabulary",
607
+ "seo_source_request",
608
+ "seo_starter_pack",
609
+ "seo_starter_pack_item",
610
+ "seo_story_angle",
611
+ "seo_topic",
612
+ "seo_web_analytics_daily",
613
+ "shared_canvas_item",
614
+ "short_link",
615
+ "skill",
616
+ "skill_render_definition",
617
+ "sms_consent",
618
+ "sms_conversation",
619
+ "sms_message",
620
+ "sms_message_media",
621
+ "sms_notification",
622
+ "sms_notification_preference",
623
+ "sms_phone_number",
624
+ "structured_list",
625
+ "studio_documents",
626
+ "studio_recording_chunks",
627
+ "studio_recording_segments",
628
+ "studio_run",
629
+ "studio_session",
630
+ "studio_session_settings",
631
+ "study_attempt",
632
+ "study_goal",
633
+ "study_media",
634
+ "study_plan",
635
+ "study_plan_block",
636
+ "study_plan_day",
637
+ "study_reminder_context",
638
+ "study_reminder_delivery",
639
+ "study_session",
640
+ "surface",
641
+ "system_context_item",
642
+ "system_error",
643
+ "system_personal_org_failure",
644
+ "system_write_failure",
645
+ "task",
646
+ "thread",
647
+ "tool",
648
+ "tool_bundle",
649
+ "tool_call",
650
+ "tool_definition_version",
651
+ "tool_test_sample",
652
+ "tool_ui",
653
+ "tool_ui_incident",
654
+ "tool_ui_version",
655
+ "transcript",
656
+ "udt_dataset_fields",
657
+ "udt_dataset_rows",
658
+ "udt_document",
659
+ "udt_structured_list_items",
660
+ "ui_surface_agent_pref",
661
+ "ui_surface_config",
662
+ "user_achievement",
663
+ "user_analysis_preference",
664
+ "user_bookmark",
665
+ "user_email_preference",
666
+ "user_feedback",
667
+ "user_form_profile",
668
+ "user_markdown_sample",
669
+ "user_memory",
670
+ "user_preference",
671
+ "user_profile",
672
+ "user_stat",
673
+ "user_surface_state",
674
+ "voice",
675
+ "war_room",
676
+ "wbx_capture",
677
+ "wbx_guidance",
678
+ "wbx_highlight",
679
+ "wbx_pattern",
680
+ "wbx_screenshot",
681
+ "wbx_seo_audit",
682
+ "wc_claim",
683
+ "wc_impairment_definition",
684
+ "wc_injury",
685
+ "wc_report",
686
+ "web_analysis_item",
687
+ "web_brand",
688
+ "web_brand_asset",
689
+ "web_brand_offering",
690
+ "web_business_fact",
691
+ "web_business_location",
692
+ "web_crawl_event",
693
+ "web_crawl_preset",
694
+ "web_crawl_schedule",
695
+ "web_crawl_session",
696
+ "web_crawl_url",
697
+ "web_discovered_item",
698
+ "web_finding",
699
+ "web_gsc_page_stat",
700
+ "web_link_edge",
701
+ "web_listing_publisher",
702
+ "web_location_listing",
703
+ "web_offering_template",
704
+ "web_page",
705
+ "web_page_content",
706
+ "web_page_evidence",
707
+ "web_page_sitemap",
708
+ "web_property",
709
+ "web_provider",
710
+ "web_result",
711
+ "web_screenshot",
712
+ "web_site",
713
+ "web_site_endpoint_rule",
714
+ "web_site_item_config",
715
+ "web_site_offering",
716
+ "web_sitemap",
717
+ "web_snapshot",
718
+ "wf_node_data_slot",
719
+ "work_item",
720
+ "workbook",
721
+ "workflow",
722
+ "workflow_card",
723
+ "workflow_checkpoint",
724
+ "workflow_definition_version",
725
+ "workflow_idempotency",
726
+ "workflow_job",
727
+ "workflow_node_events",
728
+ "workflow_node_outcome",
729
+ "workflow_plan",
730
+ "workflow_plan_event",
731
+ "workflow_plan_sample",
732
+ "workflow_recovery_audit",
733
+ "workflow_run",
734
+ "workflow_run_log",
735
+ "workflow_runtime_surface",
736
+ "workflow_template",
737
+ "workflow_trigger",
738
+ "workflow_trigger_fire",
739
+ "working_document",
740
+ "youtube_search",
741
+ "youtube_video"
742
+ ];
743
+ var ENTITY_TYPE_TOKEN_SET = new Set(ENTITY_TYPE_TOKENS);
744
+ function isEntityTypeToken(value) {
745
+ return typeof value === "string" && ENTITY_TYPE_TOKEN_SET.has(value);
746
+ }
747
+
748
+ // src/core/titles.ts
749
+ function entityTitleCacheKey(token, id) {
750
+ return `${token}:${id}`;
751
+ }
752
+
753
+ // src/core/store.ts
754
+ function associationsKey(type, id) {
755
+ return `${type}:${id}`;
756
+ }
757
+ var IDLE_ASSOCIATIONS = Object.freeze({
758
+ status: "idle",
759
+ edges: Object.freeze([]),
760
+ fetchedAt: null,
761
+ error: null
762
+ });
763
+ var IDLE_CATEGORIES = Object.freeze({
764
+ status: "idle",
765
+ categories: Object.freeze([]),
766
+ fetchedAt: null,
767
+ error: null
768
+ });
769
+
770
+ // src/react/hooks/useAssociations.ts
771
+ var noopSubscribe = () => () => {
772
+ };
773
+ function useAssociations(args) {
774
+ const { type, id, autoLoad = true } = args;
775
+ const store = useAssociationsStore();
776
+ const key = type && id ? associationsKey(type, id) : null;
777
+ const entry = useSyncExternalStore(
778
+ key ? (cb) => store.subscribe(key, cb) : noopSubscribe,
779
+ () => store.getEdges(type, id ?? ""),
780
+ () => store.getEdges(type, id ?? "")
781
+ );
782
+ const loadedKey = useRef2(null);
783
+ useEffect(() => {
784
+ if (!autoLoad || !type || !id) return;
785
+ const k = associationsKey(type, id);
786
+ if (loadedKey.current === k) return;
787
+ loadedKey.current = k;
788
+ void store.load(type, id);
789
+ }, [autoLoad, store, type, id]);
790
+ return {
791
+ edges: entry.edges,
792
+ status: entry.status,
793
+ error: entry.error,
794
+ fetchedAt: entry.fetchedAt,
795
+ add: async ({ targetType, targetId, orgId, label, role }) => {
796
+ if (!id) return { ok: false, error: "Missing entity id" };
797
+ return store.add({
798
+ sourceType: type,
799
+ sourceId: id,
800
+ targetType,
801
+ targetId,
802
+ ...orgId !== void 0 ? { orgId } : {},
803
+ ...label !== void 0 ? { label } : {},
804
+ ...role !== void 0 ? { role } : {}
805
+ });
806
+ },
807
+ remove: async ({ targetType, targetId, role }) => {
808
+ if (!id) return { ok: false, error: "Missing entity id" };
809
+ return store.remove({
810
+ sourceType: type,
811
+ sourceId: id,
812
+ targetType,
813
+ targetId,
814
+ ...role !== void 0 ? { role } : {}
815
+ });
816
+ },
817
+ setTargets: async ({ targetType, targetIds, orgId, role }) => {
818
+ if (!id) return { ok: false, error: "Missing entity id" };
819
+ return store.setTargets({
820
+ sourceType: type,
821
+ sourceId: id,
822
+ targetType,
823
+ targetIds,
824
+ ...orgId !== void 0 ? { orgId } : {},
825
+ ...role !== void 0 ? { role } : {}
826
+ });
827
+ },
828
+ reload: async () => {
829
+ if (!type || !id) return;
830
+ await store.load(type, id, { force: true });
831
+ }
832
+ };
833
+ }
834
+ var useEntityRelationships = useAssociations;
835
+
836
+ // src/react/hooks/useContainerLinks.ts
837
+ import {
838
+ useCallback,
839
+ useEffect as useEffect2,
840
+ useLayoutEffect,
841
+ useRef as useRef3,
842
+ useState
843
+ } from "react";
844
+ function useContainerLinks(args) {
845
+ const { containerType, containerId, orgId } = args;
846
+ const store = useAssociationsStore();
847
+ const {
848
+ edges,
849
+ status: genericStatus,
850
+ error: genericError,
851
+ reload: reloadGeneric
852
+ } = useAssociations({
853
+ type: containerType,
854
+ id: containerId
855
+ });
856
+ const conversationKey = containerType === "conversation" && containerId ? `conversation:${containerId}` : null;
857
+ const activeConversationKey = useRef3(conversationKey);
858
+ const conversationGeneration = useRef3(0);
859
+ const [conversationResult, setConversationResult] = useState(null);
860
+ useLayoutEffect(() => {
861
+ activeConversationKey.current = conversationKey;
862
+ conversationGeneration.current += 1;
863
+ }, [conversationKey]);
864
+ const loadConversationFiles = useCallback(async () => {
865
+ const key = conversationKey;
866
+ if (!key || !containerId) return;
867
+ if (activeConversationKey.current !== key) return;
868
+ const generation = ++conversationGeneration.current;
869
+ setConversationResult((current) => ({
870
+ key,
871
+ files: current?.key === key ? current.files : [],
872
+ // Keep the visible failure context while retrying so the same control
873
+ // can disable itself and show progress instead of disappearing.
874
+ error: current?.key === key ? current.error : null,
875
+ loading: true
876
+ }));
877
+ const result = await store.services.associations.listConversationFiles(containerId);
878
+ if (generation !== conversationGeneration.current || activeConversationKey.current !== key) {
879
+ return;
880
+ }
881
+ if (isAssociationsRpcErr(result)) {
882
+ setConversationResult((current) => ({
883
+ key,
884
+ files: current?.key === key ? current.files : [],
885
+ error: result.error.message,
886
+ loading: false
887
+ }));
888
+ return;
889
+ }
890
+ setConversationResult({
891
+ key,
892
+ files: result.data.files.map((file) => ({
893
+ edgeId: `conversation-file:${file.fileId}`,
894
+ resourceId: file.fileId,
895
+ token: "file",
896
+ role: null,
897
+ label: file.label,
898
+ metadata: file.metadata
899
+ })),
900
+ error: null,
901
+ loading: false
902
+ });
903
+ }, [containerId, conversationKey, store]);
904
+ useEffect2(() => {
905
+ const timeout = setTimeout(() => {
906
+ void loadConversationFiles();
907
+ }, 0);
908
+ return () => clearTimeout(timeout);
909
+ }, [loadConversationFiles]);
910
+ const conversationFiles = conversationResult?.key === conversationKey ? conversationResult.files : [];
911
+ const conversationFileError = conversationResult?.key === conversationKey ? conversationResult.error : null;
912
+ const conversationFileStatus = containerType !== "conversation" ? "idle" : conversationResult?.key !== conversationKey ? "loading" : conversationResult.loading ? "loading" : conversationFileError ? "error" : "ready";
913
+ const incomingGeneric = edges.filter(
914
+ (edge) => edge.direction === "incoming" && !(containerType === "conversation" && edge.otherType === "file")
915
+ ).map((edge) => ({
916
+ edgeId: edge.id,
917
+ resourceId: edge.otherId,
918
+ token: edge.otherType,
919
+ role: edge.role ?? null,
920
+ label: edge.label ?? null,
921
+ metadata: edge.metadata ?? {}
922
+ }));
923
+ const incoming = containerType === "conversation" ? [...incomingGeneric, ...conversationFiles] : incomingGeneric;
924
+ const linksFor = (token) => incoming.filter((link) => link.token === token);
925
+ const countFor = (token) => incoming.reduce((n, link) => link.token === token ? n + 1 : n, 0);
926
+ const attachedIdsFor = (token) => {
927
+ const set = /* @__PURE__ */ new Set();
928
+ for (const link of incoming) {
929
+ if (link.token === token) set.add(link.resourceId);
930
+ }
931
+ return set;
932
+ };
933
+ const attach = async (token, resourceId, label, metadata, options) => {
934
+ if (!containerId) return { ok: false, error: "Missing container id" };
935
+ const result = await store.add({
936
+ sourceType: token,
937
+ sourceId: resourceId,
938
+ targetType: containerType,
939
+ targetId: containerId,
940
+ ...orgId != null ? { orgId } : {},
941
+ ...label !== void 0 ? { label } : {},
942
+ ...metadata !== void 0 ? { metadata } : {},
943
+ ...options?.role !== void 0 ? { role: options.role } : {},
944
+ ...options?.replaceMetadata !== void 0 ? { replaceMetadata: options.replaceMetadata } : {}
945
+ });
946
+ if (result.ok && containerType === "conversation" && token === "file") {
947
+ await loadConversationFiles();
948
+ }
949
+ return result;
950
+ };
951
+ const detach = async (token, resourceId, role) => {
952
+ if (!containerId) return { ok: false, error: "Missing container id" };
953
+ const result = await store.remove({
954
+ sourceType: token,
955
+ sourceId: resourceId,
956
+ targetType: containerType,
957
+ targetId: containerId,
958
+ ...role !== void 0 ? { role } : {}
959
+ });
960
+ if (result.ok && containerType === "conversation" && token === "file") {
961
+ await loadConversationFiles();
962
+ }
963
+ return result;
964
+ };
965
+ const reload = async () => {
966
+ await Promise.all([
967
+ reloadGeneric(),
968
+ containerType === "conversation" ? loadConversationFiles() : Promise.resolve()
969
+ ]);
970
+ };
971
+ const status = !containerId ? genericStatus : conversationFileStatus === "error" ? "error" : containerType === "conversation" && (conversationFileStatus === "idle" || conversationFileStatus === "loading") ? "loading" : genericStatus;
972
+ const error = conversationFileError ?? genericError;
973
+ return {
974
+ status,
975
+ error,
976
+ reload,
977
+ totalCount: incoming.length,
978
+ countFor,
979
+ attachedIdsFor,
980
+ linksFor,
981
+ attach,
982
+ detach
983
+ };
984
+ }
985
+
986
+ // src/react/hooks/useEntityTitles.ts
987
+ import { useEffect as useEffect3, useState as useState2 } from "react";
988
+ function useEntityTitles(refs) {
989
+ const store = useAssociationsStore();
990
+ const [resolved, setResolved] = useState2({});
991
+ const [attempted, setAttempted] = useState2({});
992
+ const [loading, setLoading] = useState2(false);
993
+ const needed = refs.filter(
994
+ (r) => store.registry.tryGetEntityInfo(r.token)?.titleColumn != null && store.titles.get(r.token, r.id) == null && !attempted[entityTitleCacheKey(r.token, r.id)]
995
+ );
996
+ const neededKey = needed.map((r) => entityTitleCacheKey(r.token, r.id)).sort().join("|");
997
+ useEffect3(() => {
998
+ if (!neededKey) return;
999
+ let cancelled = false;
1000
+ setLoading(true);
1001
+ const byToken = /* @__PURE__ */ new Map();
1002
+ for (const key of neededKey.split("|")) {
1003
+ const sep = key.indexOf(":");
1004
+ const token = key.slice(0, sep);
1005
+ const id = key.slice(sep + 1);
1006
+ const list = byToken.get(token) ?? [];
1007
+ list.push(id);
1008
+ byToken.set(token, list);
1009
+ }
1010
+ void Promise.all(
1011
+ [...byToken.entries()].map(async ([token, ids]) => {
1012
+ const titles = await store.titles.fetch(token, ids);
1013
+ return [token, titles];
1014
+ })
1015
+ ).then((results) => {
1016
+ if (cancelled) return;
1017
+ setResolved((prev) => {
1018
+ const next = { ...prev };
1019
+ for (const [token, titles] of results) {
1020
+ for (const [id, title] of titles) {
1021
+ next[entityTitleCacheKey(token, id)] = title;
1022
+ }
1023
+ }
1024
+ return next;
1025
+ });
1026
+ }).catch((err) => {
1027
+ if (cancelled) return;
1028
+ store.errorSink({
1029
+ code: "title_resolution_failed",
1030
+ message: "Failed to resolve entity titles",
1031
+ context: { detail: err }
1032
+ });
1033
+ }).finally(() => {
1034
+ if (cancelled) return;
1035
+ setAttempted((prev) => {
1036
+ const next = { ...prev };
1037
+ for (const key of neededKey.split("|")) next[key] = true;
1038
+ return next;
1039
+ });
1040
+ setLoading(false);
1041
+ });
1042
+ return () => {
1043
+ cancelled = true;
1044
+ };
1045
+ }, [neededKey, store]);
1046
+ const titleFor = (ref) => {
1047
+ if (ref.label && ref.label.trim()) return ref.label;
1048
+ const key = entityTitleCacheKey(ref.token, ref.id);
1049
+ return resolved[key] ?? store.titles.get(ref.token, ref.id) ?? store.titles.fallback(ref.token);
1050
+ };
1051
+ const isUnresolved = (ref) => {
1052
+ if (!store.registry.tryGetEntityInfo(ref.token)?.titleColumn) return false;
1053
+ const key = entityTitleCacheKey(ref.token, ref.id);
1054
+ if (resolved[key] || store.titles.get(ref.token, ref.id)) return false;
1055
+ return attempted[key] === true;
1056
+ };
1057
+ return { titleFor, isUnresolved, loading };
1058
+ }
1059
+
1060
+ // src/react/hooks/useCategories.ts
1061
+ import { useEffect as useEffect4, useRef as useRef4, useSyncExternalStore as useSyncExternalStore2 } from "react";
1062
+ var noopSubscribe2 = () => () => {
1063
+ };
1064
+ function useCategories(args) {
1065
+ const { dimension, autoLoad = true } = args;
1066
+ const store = useAssociationsStore();
1067
+ const entry = useSyncExternalStore2(
1068
+ dimension ? (cb) => store.subscribeCategories(dimension, cb) : noopSubscribe2,
1069
+ () => store.getCategories(dimension ?? ""),
1070
+ () => store.getCategories(dimension ?? "")
1071
+ );
1072
+ const loadedKey = useRef4(null);
1073
+ useEffect4(() => {
1074
+ if (!autoLoad || !dimension) return;
1075
+ if (loadedKey.current === dimension) return;
1076
+ loadedKey.current = dimension;
1077
+ void store.loadCategories(dimension);
1078
+ }, [autoLoad, store, dimension]);
1079
+ return {
1080
+ categories: entry.categories,
1081
+ status: entry.status,
1082
+ error: entry.error,
1083
+ fetchedAt: entry.fetchedAt,
1084
+ create: async ({ name, orgId, parentId, color, icon, slug }) => {
1085
+ if (!dimension) return { ok: false, error: "Missing dimension" };
1086
+ return store.createCategory({
1087
+ dimension,
1088
+ name,
1089
+ orgId,
1090
+ ...parentId !== void 0 ? { parentId } : {},
1091
+ ...color !== void 0 ? { color } : {},
1092
+ ...icon !== void 0 ? { icon } : {},
1093
+ ...slug !== void 0 ? { slug } : {}
1094
+ });
1095
+ },
1096
+ update: async ({ id, name, slug, color, icon, position }) => {
1097
+ if (!dimension) return { ok: false, error: "Missing dimension" };
1098
+ return store.updateCategory({
1099
+ dimension,
1100
+ id,
1101
+ name,
1102
+ slug,
1103
+ color,
1104
+ icon,
1105
+ position
1106
+ });
1107
+ },
1108
+ reparent: async (id, parentId) => {
1109
+ if (!dimension) return { ok: false, error: "Missing dimension" };
1110
+ return store.reparentCategory({ dimension, id, parentId });
1111
+ },
1112
+ remove: async (id) => {
1113
+ if (!dimension) return { ok: false, error: "Missing dimension" };
1114
+ return store.deleteCategory({ dimension, id });
1115
+ },
1116
+ reload: async () => {
1117
+ if (!dimension) return;
1118
+ await store.loadCategories(dimension, { force: true });
1119
+ }
1120
+ };
1121
+ }
1122
+
1123
+ // src/react/hooks/useUniversalEntitySearch.ts
1124
+ import { useEffect as useEffect5, useRef as useRef5, useState as useState3 } from "react";
1125
+ var DEBOUNCE_MS = 250;
1126
+ var RECENTS_LIMIT = 12;
1127
+ function useUniversalEntitySearch(args) {
1128
+ const {
1129
+ query,
1130
+ tokens,
1131
+ ownerId,
1132
+ enabled = true,
1133
+ perTokenLimit = 5,
1134
+ emptyQueryMode = "recents"
1135
+ } = args;
1136
+ const store = useAssociationsStore();
1137
+ const [results, setResults] = useState3([]);
1138
+ const [loading, setLoading] = useState3(false);
1139
+ const [isRecents, setIsRecents] = useState3(true);
1140
+ const runRef = useRef5(0);
1141
+ const tokensKey = (tokens ?? []).join(",");
1142
+ useEffect5(() => {
1143
+ if (!enabled) return;
1144
+ const run = ++runRef.current;
1145
+ const trimmed = query.trim();
1146
+ const timer = setTimeout(async () => {
1147
+ if (runRef.current !== run) return;
1148
+ setLoading(true);
1149
+ const activeTokens = tokensKey ? tokensKey.split(",") : store.registry.curatedTokens();
1150
+ let next = [];
1151
+ if (trimmed || emptyQueryMode === "candidates") {
1152
+ next = await store.candidates.searchCandidatesAcrossTokens({
1153
+ tokens: activeTokens,
1154
+ search: trimmed,
1155
+ ownerId: ownerId ?? null,
1156
+ perTokenLimit
1157
+ });
1158
+ } else {
1159
+ next = await loadRecents(store, activeTokens);
1160
+ }
1161
+ if (runRef.current !== run) return;
1162
+ setResults(next);
1163
+ setIsRecents(!trimmed && emptyQueryMode === "recents");
1164
+ setLoading(false);
1165
+ }, DEBOUNCE_MS);
1166
+ return () => clearTimeout(timer);
1167
+ }, [query, tokensKey, ownerId, enabled, perTokenLimit, emptyQueryMode, store]);
1168
+ return { results, loading, isRecents };
1169
+ }
1170
+ async function loadRecents(store, tokens) {
1171
+ const res = await store.favorites.list();
1172
+ if (!res.ok) return [];
1173
+ const tokenSet = new Set(tokens);
1174
+ const rows = res.data.items.filter(
1175
+ (i) => tokenSet.has(i.entityType) && isEntityTypeToken(i.entityType) && i.lastViewedAt
1176
+ ).sort((a, b) => (b.lastViewedAt ?? "").localeCompare(a.lastViewedAt ?? "")).slice(0, RECENTS_LIMIT);
1177
+ if (rows.length === 0) return [];
1178
+ const byToken = /* @__PURE__ */ new Map();
1179
+ for (const r of rows) {
1180
+ const list = byToken.get(r.entityType) ?? [];
1181
+ list.push(r.entityId);
1182
+ byToken.set(r.entityType, list);
1183
+ }
1184
+ const titles = /* @__PURE__ */ new Map();
1185
+ await Promise.all(
1186
+ [...byToken.entries()].map(async ([token, ids]) => {
1187
+ titles.set(token, await store.titles.fetch(token, ids));
1188
+ })
1189
+ );
1190
+ const out = [];
1191
+ for (const r of rows) {
1192
+ const title = titles.get(r.entityType)?.get(r.entityId);
1193
+ if (!title) continue;
1194
+ out.push({
1195
+ token: r.entityType,
1196
+ id: r.entityId,
1197
+ title
1198
+ });
1199
+ }
1200
+ return out;
1201
+ }
1202
+
1203
+ // src/react/hooks/useAssociationCandidates.ts
1204
+ import { useEffect as useEffect6, useState as useState4 } from "react";
1205
+ function useAssociationCandidates(args) {
1206
+ const { token, enabled = true, search, limit } = args;
1207
+ const store = useAssociationsStore();
1208
+ const userId = currentUserIdOrNull(store);
1209
+ const [candidates, setCandidates] = useState4([]);
1210
+ const [loading, setLoading] = useState4(false);
1211
+ const [error, setError] = useState4(null);
1212
+ const [tick, setTick] = useState4(0);
1213
+ useEffect6(() => {
1214
+ if (!enabled) return void 0;
1215
+ let cancelled = false;
1216
+ void (async () => {
1217
+ setLoading(true);
1218
+ setError(null);
1219
+ const res = await store.candidates.listAssociationCandidates({
1220
+ token,
1221
+ ownerId: userId,
1222
+ ...search !== void 0 ? { search } : {},
1223
+ ...limit !== void 0 ? { limit } : {}
1224
+ });
1225
+ if (cancelled) return;
1226
+ if (res.ok) {
1227
+ setCandidates(res.data);
1228
+ } else {
1229
+ setError(res.error);
1230
+ setCandidates([]);
1231
+ }
1232
+ setLoading(false);
1233
+ })();
1234
+ return () => {
1235
+ cancelled = true;
1236
+ };
1237
+ }, [token, enabled, userId, search, limit, tick, store]);
1238
+ return {
1239
+ candidates,
1240
+ loading,
1241
+ error,
1242
+ reload: () => setTick((t) => t + 1)
1243
+ };
1244
+ }
1245
+ function currentUserIdOrNull(store) {
1246
+ try {
1247
+ return store.identity.requireUserId();
1248
+ } catch {
1249
+ return null;
1250
+ }
1251
+ }
1252
+
1253
+ // src/react/hooks/useAssociationEntitySelect.ts
1254
+ import { useState as useState5 } from "react";
1255
+ function useAssociationEntitySelectAdapter(args) {
1256
+ const { token, container, activeId, onActiveChange, createColumns } = args;
1257
+ const store = useAssociationsStore();
1258
+ const notifier = useNotifier();
1259
+ const links = useContainerLinks({
1260
+ containerType: container.type,
1261
+ containerId: container.id,
1262
+ orgId: container.orgId ?? null
1263
+ });
1264
+ const rows = links.linksFor(token);
1265
+ const { titleFor } = useEntityTitles(
1266
+ rows.map((r) => ({ token, id: r.resourceId, label: r.label }))
1267
+ );
1268
+ const [titleOverrides, setTitleOverrides] = useState5(
1269
+ {}
1270
+ );
1271
+ const [localActiveId, setLocalActiveId] = useState5(null);
1272
+ const items = rows.map((r) => ({
1273
+ id: r.resourceId,
1274
+ title: titleOverrides[r.resourceId] ?? titleFor({ token, id: r.resourceId, label: r.label })
1275
+ }));
1276
+ const controlled = activeId !== void 0;
1277
+ const resolvedActive = controlled ? activeId : localActiveId;
1278
+ const effectiveActive = resolvedActive && items.some((i) => i.id === resolvedActive) ? resolvedActive : items[0]?.id ?? null;
1279
+ const setActive = (id) => {
1280
+ if (!controlled) setLocalActiveId(id);
1281
+ return onActiveChange?.(id);
1282
+ };
1283
+ return {
1284
+ loading: links.status === "loading" || links.status === "idle",
1285
+ items,
1286
+ activeId: effectiveActive,
1287
+ setActive,
1288
+ createAndAttach: async (title) => {
1289
+ const created = await store.entityRows.createEntityRow(token, {
1290
+ title,
1291
+ orgId: container.orgId ?? null,
1292
+ ...createColumns !== void 0 ? { extraColumns: createColumns } : {}
1293
+ });
1294
+ if (!created.ok) return null;
1295
+ let attached = await links.attach(token, created.data.id, title);
1296
+ if (!attached.ok) {
1297
+ attached = await links.attach(token, created.data.id, title);
1298
+ }
1299
+ if (!attached.ok) {
1300
+ store.errorSink({
1301
+ code: "created_but_attach_failed",
1302
+ message: "[useAssociationEntitySelectAdapter] created but attach failed",
1303
+ context: {
1304
+ token,
1305
+ id: created.data.id,
1306
+ error: attached.error
1307
+ }
1308
+ });
1309
+ notifier.error(
1310
+ `Created "${title}" but couldn't link it here \u2014 it exists in your library`
1311
+ );
1312
+ return null;
1313
+ }
1314
+ setTitleOverrides((prev) => ({ ...prev, [created.data.id]: title }));
1315
+ await setActive(created.data.id);
1316
+ return created.data.id;
1317
+ },
1318
+ rename: async (id, title) => {
1319
+ const res = await store.entityRows.renameEntityRow(token, id, title);
1320
+ if (res.ok) setTitleOverrides((prev) => ({ ...prev, [id]: title.trim() }));
1321
+ return res.ok;
1322
+ },
1323
+ detach: (id) => links.detach(token, id)
1324
+ };
1325
+ }
1326
+
1327
+ // src/react/components/PrimaryEntityContext.tsx
1328
+ import { createContext as createContext2, useContext as useContext2 } from "react";
1329
+ import { jsx as jsx2 } from "react/jsx-runtime";
1330
+ var PrimaryEntityCtx = createContext2(null);
1331
+ function PrimaryEntityProvider({
1332
+ value,
1333
+ children
1334
+ }) {
1335
+ return /* @__PURE__ */ jsx2(PrimaryEntityCtx.Provider, { value, children });
1336
+ }
1337
+ function usePrimaryEntity() {
1338
+ return useContext2(PrimaryEntityCtx);
1339
+ }
1340
+
1341
+ // src/react/components/AssociationWindow.tsx
1342
+ import { useId } from "react";
1343
+ import { cn } from "@ai-matrx/design-system";
1344
+
1345
+ // src/react/icons.tsx
1346
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
1347
+ function base(props) {
1348
+ return {
1349
+ xmlns: "http://www.w3.org/2000/svg",
1350
+ width: 24,
1351
+ height: 24,
1352
+ viewBox: "0 0 24 24",
1353
+ fill: "none",
1354
+ stroke: "currentColor",
1355
+ strokeWidth: 2,
1356
+ strokeLinecap: "round",
1357
+ strokeLinejoin: "round",
1358
+ "aria-hidden": true,
1359
+ ...props
1360
+ };
1361
+ }
1362
+ function SpinnerIcon(props) {
1363
+ return /* @__PURE__ */ jsx3("svg", { ...base(props), children: /* @__PURE__ */ jsx3("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) });
1364
+ }
1365
+ function PlusIcon(props) {
1366
+ return /* @__PURE__ */ jsxs("svg", { ...base(props), children: [
1367
+ /* @__PURE__ */ jsx3("path", { d: "M5 12h14" }),
1368
+ /* @__PURE__ */ jsx3("path", { d: "M12 5v14" })
1369
+ ] });
1370
+ }
1371
+ function XIcon(props) {
1372
+ return /* @__PURE__ */ jsxs("svg", { ...base(props), children: [
1373
+ /* @__PURE__ */ jsx3("path", { d: "M18 6 6 18" }),
1374
+ /* @__PURE__ */ jsx3("path", { d: "m6 6 12 12" })
1375
+ ] });
1376
+ }
1377
+ function CheckIcon(props) {
1378
+ return /* @__PURE__ */ jsx3("svg", { ...base(props), children: /* @__PURE__ */ jsx3("path", { d: "M20 6 9 17l-5-5" }) });
1379
+ }
1380
+ function SearchIcon(props) {
1381
+ return /* @__PURE__ */ jsxs("svg", { ...base(props), children: [
1382
+ /* @__PURE__ */ jsx3("circle", { cx: "11", cy: "11", r: "8" }),
1383
+ /* @__PURE__ */ jsx3("path", { d: "m21 21-4.3-4.3" })
1384
+ ] });
1385
+ }
1386
+ function Link2Icon(props) {
1387
+ return /* @__PURE__ */ jsxs("svg", { ...base(props), children: [
1388
+ /* @__PURE__ */ jsx3("path", { d: "M9 17H7A5 5 0 0 1 7 7h2" }),
1389
+ /* @__PURE__ */ jsx3("path", { d: "M15 7h2a5 5 0 1 1 0 10h-2" }),
1390
+ /* @__PURE__ */ jsx3("line", { x1: "8", x2: "16", y1: "12", y2: "12" })
1391
+ ] });
1392
+ }
1393
+ function RefreshIcon(props) {
1394
+ return /* @__PURE__ */ jsxs("svg", { ...base(props), children: [
1395
+ /* @__PURE__ */ jsx3("path", { d: "M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" }),
1396
+ /* @__PURE__ */ jsx3("path", { d: "M21 3v5h-5" }),
1397
+ /* @__PURE__ */ jsx3("path", { d: "M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" }),
1398
+ /* @__PURE__ */ jsx3("path", { d: "M8 16H3v5" })
1399
+ ] });
1400
+ }
1401
+ function PinIcon(props) {
1402
+ return /* @__PURE__ */ jsxs("svg", { ...base(props), children: [
1403
+ /* @__PURE__ */ jsx3("path", { d: "M12 17v5" }),
1404
+ /* @__PURE__ */ jsx3("path", { d: "M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8a2 2 0 0 0 0 4 1 1 0 0 1 1 1z" })
1405
+ ] });
1406
+ }
1407
+ function UploadIcon(props) {
1408
+ return /* @__PURE__ */ jsxs("svg", { ...base(props), children: [
1409
+ /* @__PURE__ */ jsx3("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
1410
+ /* @__PURE__ */ jsx3("polyline", { points: "17 8 12 3 7 8" }),
1411
+ /* @__PURE__ */ jsx3("line", { x1: "12", x2: "12", y1: "3", y2: "15" })
1412
+ ] });
1413
+ }
1414
+ function FolderOpenIcon(props) {
1415
+ return /* @__PURE__ */ jsx3("svg", { ...base(props), children: /* @__PURE__ */ jsx3("path", { d: "m6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2" }) });
1416
+ }
1417
+ function ExternalLinkIcon(props) {
1418
+ return /* @__PURE__ */ jsxs("svg", { ...base(props), children: [
1419
+ /* @__PURE__ */ jsx3("path", { d: "M15 3h6v6" }),
1420
+ /* @__PURE__ */ jsx3("path", { d: "M10 14 21 3" }),
1421
+ /* @__PURE__ */ jsx3("path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" })
1422
+ ] });
1423
+ }
1424
+ function ChevronDownIcon(props) {
1425
+ return /* @__PURE__ */ jsx3("svg", { ...base(props), children: /* @__PURE__ */ jsx3("path", { d: "m6 9 6 6 6-6" }) });
1426
+ }
1427
+ function ChevronsUpDownIcon(props) {
1428
+ return /* @__PURE__ */ jsxs("svg", { ...base(props), children: [
1429
+ /* @__PURE__ */ jsx3("path", { d: "m7 15 5 5 5-5" }),
1430
+ /* @__PURE__ */ jsx3("path", { d: "m7 9 5-5 5 5" })
1431
+ ] });
1432
+ }
1433
+ function CornerDownRightIcon(props) {
1434
+ return /* @__PURE__ */ jsxs("svg", { ...base(props), children: [
1435
+ /* @__PURE__ */ jsx3("polyline", { points: "15 10 20 15 15 20" }),
1436
+ /* @__PURE__ */ jsx3("path", { d: "M4 4v7a4 4 0 0 0 4 4h12" })
1437
+ ] });
1438
+ }
1439
+ function TagIcon(props) {
1440
+ return /* @__PURE__ */ jsxs("svg", { ...base(props), children: [
1441
+ /* @__PURE__ */ jsx3("path", { d: "M12.586 2.586A2 2 0 0 0 11.172 2H4a2 2 0 0 0-2 2v7.172a2 2 0 0 0 .586 1.414l8.704 8.704a2.426 2.426 0 0 0 3.42 0l6.58-6.58a2.426 2.426 0 0 0 0-3.42z" }),
1442
+ /* @__PURE__ */ jsx3("circle", { cx: "7.5", cy: "7.5", r: ".5", fill: "currentColor" })
1443
+ ] });
1444
+ }
1445
+ function DefaultEntityIcon(props) {
1446
+ return /* @__PURE__ */ jsxs("svg", { ...base(props), children: [
1447
+ /* @__PURE__ */ jsx3("path", { d: "M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z" }),
1448
+ /* @__PURE__ */ jsx3("path", { d: "m7 16.5-4.74-2.85" }),
1449
+ /* @__PURE__ */ jsx3("path", { d: "m7 16.5 5-3" }),
1450
+ /* @__PURE__ */ jsx3("path", { d: "M7 16.5v5.17" }),
1451
+ /* @__PURE__ */ jsx3("path", { d: "M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z" }),
1452
+ /* @__PURE__ */ jsx3("path", { d: "m17 16.5-5-3" }),
1453
+ /* @__PURE__ */ jsx3("path", { d: "m17 16.5 4.74-2.85" }),
1454
+ /* @__PURE__ */ jsx3("path", { d: "M17 16.5v5.17" }),
1455
+ /* @__PURE__ */ jsx3("path", { d: "M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0l-3 1.8Z" }),
1456
+ /* @__PURE__ */ jsx3("path", { d: "M12 8 7.26 5.15" }),
1457
+ /* @__PURE__ */ jsx3("path", { d: "m12 8 4.74-2.85" }),
1458
+ /* @__PURE__ */ jsx3("path", { d: "M12 13.5V8" })
1459
+ ] });
1460
+ }
1461
+
1462
+ // src/react/components/AssociationWindow.tsx
1463
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
1464
+ function AssociationWindow({
1465
+ open,
1466
+ onClose,
1467
+ scopeId,
1468
+ title,
1469
+ icon,
1470
+ subtitle,
1471
+ children
1472
+ }) {
1473
+ const instanceId = useId();
1474
+ const { windowShell } = useAssociationsUiPorts();
1475
+ if (!open) return null;
1476
+ const body = /* @__PURE__ */ jsxs2("div", { className: "flex h-full min-h-0 flex-col gap-2 p-3", children: [
1477
+ subtitle ? /* @__PURE__ */ jsx4("div", { className: "shrink-0 text-xs text-muted-foreground", children: subtitle }) : null,
1478
+ children
1479
+ ] });
1480
+ if (windowShell) {
1481
+ const HostWindow = windowShell.Window;
1482
+ return /* @__PURE__ */ jsx4(
1483
+ HostWindow,
1484
+ {
1485
+ id: `association:${scopeId}:${instanceId}`,
1486
+ title,
1487
+ onClose,
1488
+ children: body
1489
+ }
1490
+ );
1491
+ }
1492
+ return /* @__PURE__ */ jsxs2(
1493
+ "div",
1494
+ {
1495
+ role: "dialog",
1496
+ "aria-label": title,
1497
+ className: cn(
1498
+ "pointer-events-auto fixed left-1/2 top-1/2 z-50 flex max-h-[85dvh] w-[min(440px,calc(100vw-2rem))] -translate-x-1/2 -translate-y-1/2 flex-col overflow-hidden rounded-xl border border-border bg-card shadow-xl"
1499
+ ),
1500
+ style: { height: "min(580px, 85dvh)" },
1501
+ children: [
1502
+ /* @__PURE__ */ jsxs2("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-border px-3 py-2", children: [
1503
+ /* @__PURE__ */ jsxs2("span", { className: "flex min-w-0 items-center gap-1.5 text-sm font-medium text-foreground", children: [
1504
+ icon,
1505
+ /* @__PURE__ */ jsx4("span", { className: "truncate", children: title })
1506
+ ] }),
1507
+ /* @__PURE__ */ jsx4(
1508
+ "button",
1509
+ {
1510
+ type: "button",
1511
+ onClick: onClose,
1512
+ "aria-label": "Close",
1513
+ className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-foreground",
1514
+ children: /* @__PURE__ */ jsx4(XIcon, { className: "h-3.5 w-3.5" })
1515
+ }
1516
+ )
1517
+ ] }),
1518
+ /* @__PURE__ */ jsx4("div", { className: "min-h-0 flex-1", children: body })
1519
+ ]
1520
+ }
1521
+ );
1522
+ }
1523
+
1524
+ // src/react/components/AssociationPicker.tsx
1525
+ import { useState as useState6 } from "react";
1526
+ import { cn as cn2, Input } from "@ai-matrx/design-system";
1527
+ import { Fragment, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
1528
+ function AssociationPicker(props) {
1529
+ const store = useAssociationsStore();
1530
+ const { pickerOverrides } = useAssociationsUiPorts();
1531
+ const info = store.registry.getEntityInfo(props.token);
1532
+ const Icon = info.Icon ?? DefaultEntityIcon;
1533
+ const Override = pickerOverrides?.[props.token];
1534
+ if (Override) return /* @__PURE__ */ jsx5(Override, { ...props });
1535
+ const title = `Add ${info.labelPlural}`;
1536
+ const subtitle = props.containerLabel ? `Attach to ${props.containerLabel}` : "Click an item to attach or detach it";
1537
+ if (!props.open) return null;
1538
+ return /* @__PURE__ */ jsx5(
1539
+ AssociationWindow,
1540
+ {
1541
+ open: props.open,
1542
+ onClose: () => props.onOpenChange(false),
1543
+ scopeId: `picker:${props.token}`,
1544
+ title,
1545
+ icon: /* @__PURE__ */ jsx5(Icon, { className: "size-3.5 text-primary" }),
1546
+ subtitle,
1547
+ children: /* @__PURE__ */ jsx5(
1548
+ AssociationCandidateBody,
1549
+ {
1550
+ token: props.token,
1551
+ enabled: props.open,
1552
+ orgId: props.orgId,
1553
+ attachedIds: props.attachedIds,
1554
+ onAttach: props.onAttach,
1555
+ onDetach: props.onDetach,
1556
+ onClose: () => props.onOpenChange(false)
1557
+ }
1558
+ )
1559
+ }
1560
+ );
1561
+ }
1562
+ function AssociationCandidateBody({
1563
+ token,
1564
+ enabled,
1565
+ orgId,
1566
+ attachedIds,
1567
+ onAttach,
1568
+ onDetach,
1569
+ onClose
1570
+ }) {
1571
+ void onClose;
1572
+ return /* @__PURE__ */ jsx5(
1573
+ EntityCandidateList,
1574
+ {
1575
+ token,
1576
+ enabled,
1577
+ orgId,
1578
+ attachedIds,
1579
+ onAttach,
1580
+ onDetach
1581
+ }
1582
+ );
1583
+ }
1584
+ function EntityCandidateList({
1585
+ token,
1586
+ enabled,
1587
+ orgId,
1588
+ attachedIds,
1589
+ onAttach,
1590
+ onDetach
1591
+ }) {
1592
+ const store = useAssociationsStore();
1593
+ const notifier = useNotifier();
1594
+ const [search, setSearch] = useState6("");
1595
+ const [busyId, setBusyId] = useState6(null);
1596
+ const info = store.registry.getEntityInfo(token);
1597
+ const Icon = info.Icon ?? DefaultEntityIcon;
1598
+ const { candidates, loading, error, reload } = useAssociationCandidates({
1599
+ token,
1600
+ enabled,
1601
+ ...search.trim() ? { search: search.trim() } : {}
1602
+ });
1603
+ const canCreate = info.titleColumn !== null && info.listCandidates === null;
1604
+ const toggle = async (id, title) => {
1605
+ if (busyId) return;
1606
+ setBusyId(id);
1607
+ try {
1608
+ const attached = attachedIds.has(id);
1609
+ const res = attached ? await onDetach(id) : await onAttach(id, title);
1610
+ if (!res.ok) {
1611
+ notifier.error(
1612
+ `Couldn't ${attached ? "detach" : "attach"} "${title}"` + (res.error ? `: ${res.error}` : "")
1613
+ );
1614
+ }
1615
+ } finally {
1616
+ setBusyId(null);
1617
+ }
1618
+ };
1619
+ return /* @__PURE__ */ jsxs3(Fragment, { children: [
1620
+ /* @__PURE__ */ jsxs3("div", { className: "relative mb-2", children: [
1621
+ /* @__PURE__ */ jsx5(SearchIcon, { className: "absolute left-2.5 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" }),
1622
+ /* @__PURE__ */ jsx5(
1623
+ Input,
1624
+ {
1625
+ value: search,
1626
+ onChange: (e) => setSearch(e.target.value),
1627
+ placeholder: `Search ${info.labelPlural.toLowerCase()}\u2026`,
1628
+ className: "h-11 pl-8 text-base md:h-9",
1629
+ style: { fontSize: 16 }
1630
+ }
1631
+ )
1632
+ ] }),
1633
+ /* @__PURE__ */ jsx5("div", { className: "flex-1 min-h-0 overflow-y-auto -mx-1 px-1", children: loading && candidates.length === 0 ? /* @__PURE__ */ jsxs3(ListMessage, { children: [
1634
+ /* @__PURE__ */ jsx5(SpinnerIcon, { className: "h-4 w-4 animate-spin" }),
1635
+ "Loading\u2026"
1636
+ ] }) : error ? /* @__PURE__ */ jsxs3("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-[12px] text-destructive", children: [
1637
+ /* @__PURE__ */ jsx5("p", { className: "font-medium", children: "Couldn\u2019t load items" }),
1638
+ /* @__PURE__ */ jsx5("p", { className: "opacity-80", children: error }),
1639
+ /* @__PURE__ */ jsx5(
1640
+ "button",
1641
+ {
1642
+ type: "button",
1643
+ onClick: reload,
1644
+ className: "mt-1 underline hover:no-underline",
1645
+ children: "Retry"
1646
+ }
1647
+ )
1648
+ ] }) : candidates.length === 0 ? /* @__PURE__ */ jsx5(ListMessage, { children: canCreate ? `Nothing to attach yet \u2014 create a new ${info.label.toLowerCase()} below.` : "Nothing to attach." }) : /* @__PURE__ */ jsx5("ul", { className: "space-y-0.5", children: candidates.map((c) => {
1649
+ const attached = attachedIds.has(c.id);
1650
+ const busy = busyId === c.id;
1651
+ return /* @__PURE__ */ jsx5("li", { children: /* @__PURE__ */ jsxs3(
1652
+ "button",
1653
+ {
1654
+ type: "button",
1655
+ disabled: busy,
1656
+ onClick: () => toggle(c.id, c.title),
1657
+ className: cn2(
1658
+ "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",
1659
+ "hover:bg-accent disabled:opacity-50",
1660
+ attached && "bg-accent/40"
1661
+ ),
1662
+ children: [
1663
+ /* @__PURE__ */ jsx5(Icon, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
1664
+ /* @__PURE__ */ jsx5("span", { className: "flex-1 min-w-0 truncate text-foreground", children: c.title }),
1665
+ /* @__PURE__ */ jsx5(
1666
+ "span",
1667
+ {
1668
+ className: cn2(
1669
+ "flex h-5 w-5 items-center justify-center rounded-full shrink-0",
1670
+ attached ? "bg-primary text-primary-foreground" : "border border-border text-muted-foreground group-hover:border-primary/60"
1671
+ ),
1672
+ children: busy ? /* @__PURE__ */ jsx5(SpinnerIcon, { className: "h-3 w-3 animate-spin" }) : attached ? /* @__PURE__ */ jsx5(CheckIcon, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx5(PlusIcon, { className: "h-3 w-3" })
1673
+ }
1674
+ )
1675
+ ]
1676
+ }
1677
+ ) }, c.id);
1678
+ }) }) }),
1679
+ canCreate && /* @__PURE__ */ jsx5(
1680
+ CreateAndAttachFooter,
1681
+ {
1682
+ token,
1683
+ orgId,
1684
+ seed: search.trim(),
1685
+ onAttach,
1686
+ onCreated: reload
1687
+ }
1688
+ )
1689
+ ] });
1690
+ }
1691
+ function CreateAndAttachFooter({
1692
+ token,
1693
+ orgId,
1694
+ seed,
1695
+ onAttach,
1696
+ onCreated
1697
+ }) {
1698
+ const store = useAssociationsStore();
1699
+ const notifier = useNotifier();
1700
+ const info = store.registry.getEntityInfo(token);
1701
+ const [editing, setEditing] = useState6(false);
1702
+ const [name, setName] = useState6("");
1703
+ const [busy, setBusy] = useState6(false);
1704
+ const submit = async () => {
1705
+ const title = name.trim();
1706
+ if (!title || busy) return;
1707
+ setBusy(true);
1708
+ try {
1709
+ const created = await store.entityRows.createEntityRow(token, {
1710
+ title,
1711
+ orgId: orgId ?? null
1712
+ });
1713
+ if (!created.ok) {
1714
+ notifier.error(
1715
+ `Couldn't create ${info.label.toLowerCase()}: ${created.error}`
1716
+ );
1717
+ return;
1718
+ }
1719
+ let attached = await onAttach(created.data.id, created.data.title);
1720
+ if (!attached.ok) {
1721
+ attached = await onAttach(created.data.id, created.data.title);
1722
+ }
1723
+ if (attached.ok) {
1724
+ notifier.success(`"${title}" created and attached`);
1725
+ } else {
1726
+ notifier.error(
1727
+ `Created "${title}" but couldn't attach it \u2014 it's saved in your ${info.labelPlural.toLowerCase()}; pick it from the list to retry.`
1728
+ );
1729
+ }
1730
+ setEditing(false);
1731
+ setName("");
1732
+ onCreated();
1733
+ } finally {
1734
+ setBusy(false);
1735
+ }
1736
+ };
1737
+ if (!editing) {
1738
+ return /* @__PURE__ */ jsxs3(
1739
+ "button",
1740
+ {
1741
+ type: "button",
1742
+ onClick: () => {
1743
+ setName(seed);
1744
+ setEditing(true);
1745
+ },
1746
+ className: "mt-2 flex min-h-11 w-full shrink-0 items-center justify-center gap-1.5 rounded-md border border-dashed border-border px-3 py-2 text-sm text-muted-foreground transition-colors hover:border-primary/60 hover:text-foreground md:min-h-0",
1747
+ children: [
1748
+ /* @__PURE__ */ jsx5(PlusIcon, { className: "h-4 w-4" }),
1749
+ "New ",
1750
+ info.label,
1751
+ seed ? /* @__PURE__ */ jsxs3("span", { className: "max-w-40 truncate text-muted-foreground/70", children: [
1752
+ "\u201C",
1753
+ seed,
1754
+ "\u201D"
1755
+ ] }) : null
1756
+ ]
1757
+ }
1758
+ );
1759
+ }
1760
+ return /* @__PURE__ */ jsxs3("div", { className: "mt-2 flex shrink-0 items-center gap-1.5", children: [
1761
+ /* @__PURE__ */ jsx5(
1762
+ Input,
1763
+ {
1764
+ autoFocus: true,
1765
+ value: name,
1766
+ onChange: (e) => setName(e.target.value),
1767
+ onKeyDown: (e) => {
1768
+ if (e.key === "Enter") void submit();
1769
+ if (e.key === "Escape") {
1770
+ setEditing(false);
1771
+ setName("");
1772
+ }
1773
+ },
1774
+ placeholder: `New ${info.label.toLowerCase()} name\u2026`,
1775
+ disabled: busy,
1776
+ className: "h-11 flex-1 text-base md:h-8",
1777
+ style: { fontSize: 16 }
1778
+ }
1779
+ ),
1780
+ /* @__PURE__ */ jsx5(
1781
+ "button",
1782
+ {
1783
+ type: "button",
1784
+ disabled: busy || !name.trim(),
1785
+ onClick: () => void submit(),
1786
+ className: "flex h-11 items-center gap-1 rounded-md bg-primary px-2.5 text-sm font-medium text-primary-foreground transition-opacity disabled:opacity-50 md:h-8",
1787
+ children: busy ? /* @__PURE__ */ jsx5(SpinnerIcon, { className: "h-3.5 w-3.5 animate-spin" }) : "Create"
1788
+ }
1789
+ ),
1790
+ /* @__PURE__ */ jsx5(
1791
+ "button",
1792
+ {
1793
+ type: "button",
1794
+ disabled: busy,
1795
+ onClick: () => {
1796
+ setEditing(false);
1797
+ setName("");
1798
+ },
1799
+ title: "Cancel",
1800
+ className: "flex h-11 w-11 items-center justify-center rounded-md border border-border text-muted-foreground transition-colors hover:text-foreground disabled:opacity-50 md:h-8 md:w-8",
1801
+ children: /* @__PURE__ */ jsx5(XIcon, { className: "h-4 w-4" })
1802
+ }
1803
+ )
1804
+ ] });
1805
+ }
1806
+ function ListMessage({ children }) {
1807
+ return /* @__PURE__ */ jsx5("div", { className: "flex items-center justify-center gap-2 py-8 text-[13px] text-muted-foreground", children });
1808
+ }
1809
+
1810
+ // src/react/components/UniversalAssociationPicker.tsx
1811
+ import { useState as useState7 } from "react";
1812
+ import { cn as cn3, Input as Input2 } from "@ai-matrx/design-system";
1813
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
1814
+ function attachedKey(token, id) {
1815
+ return `${token}:${id}`;
1816
+ }
1817
+ function UniversalAssociationPicker(props) {
1818
+ const { attachedKeys, onAttach, onDetach, ownerId, orgId, className } = props;
1819
+ const store = useAssociationsStore();
1820
+ const { entityDoors, pickerOverrides } = useAssociationsUiPorts();
1821
+ const notifier = useNotifier();
1822
+ const tokens = props.tokens ?? store.registry.curatedTokens();
1823
+ const [query, setQuery] = useState7("");
1824
+ const [browseToken, setBrowseToken] = useState7(null);
1825
+ const [busyKey, setBusyKey] = useState7(null);
1826
+ const { results, loading, isRecents } = useUniversalEntitySearch({
1827
+ query,
1828
+ tokens,
1829
+ ownerId: ownerId ?? null,
1830
+ enabled: browseToken === null
1831
+ });
1832
+ const DoorControls = entityDoors?.DoorControls;
1833
+ const toggle = async (c) => {
1834
+ const key = attachedKey(c.token, c.id);
1835
+ if (busyKey) return;
1836
+ setBusyKey(key);
1837
+ try {
1838
+ const attached = attachedKeys.has(key);
1839
+ const res = attached ? await onDetach(c.token, c.id) : await onAttach(c.token, c.id, c.title);
1840
+ if (!res.ok) {
1841
+ notifier.error(
1842
+ `Couldn't ${attached ? "detach" : "attach"} "${c.title}"` + (res.error ? `: ${res.error}` : "")
1843
+ );
1844
+ }
1845
+ } finally {
1846
+ setBusyKey(null);
1847
+ }
1848
+ };
1849
+ const groups = /* @__PURE__ */ new Map();
1850
+ for (const r of results) {
1851
+ const list = groups.get(r.token) ?? [];
1852
+ list.push(r);
1853
+ groups.set(r.token, list);
1854
+ }
1855
+ const BrowseOverride = browseToken ? pickerOverrides?.[browseToken] : void 0;
1856
+ return /* @__PURE__ */ jsxs4("div", { className: cn3("flex min-h-0 flex-1 flex-col gap-2", className), children: [
1857
+ /* @__PURE__ */ jsxs4("div", { className: "relative", children: [
1858
+ /* @__PURE__ */ jsx6(SearchIcon, { className: "absolute left-2.5 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }),
1859
+ /* @__PURE__ */ jsx6(
1860
+ Input2,
1861
+ {
1862
+ value: query,
1863
+ onChange: (e) => {
1864
+ setQuery(e.target.value);
1865
+ if (browseToken) setBrowseToken(null);
1866
+ },
1867
+ placeholder: "Search everything\u2026",
1868
+ className: "pl-8 text-base",
1869
+ style: { fontSize: 16 }
1870
+ }
1871
+ ),
1872
+ loading && browseToken === null && /* @__PURE__ */ jsx6(SpinnerIcon, { className: "absolute right-2.5 top-1/2 h-4 w-4 -translate-y-1/2 animate-spin text-muted-foreground" })
1873
+ ] }),
1874
+ /* @__PURE__ */ jsx6("div", { className: "flex flex-wrap gap-1", children: tokens.map((t) => {
1875
+ const info = store.registry.getEntityInfo(t);
1876
+ const ChipIcon = info.Icon ?? DefaultEntityIcon;
1877
+ const active = browseToken === t;
1878
+ return /* @__PURE__ */ jsxs4(
1879
+ "button",
1880
+ {
1881
+ type: "button",
1882
+ onClick: () => setBrowseToken(active ? null : t),
1883
+ className: cn3(
1884
+ "inline-flex items-center gap-1 rounded-md border px-1.5 py-0.5 text-[11px] transition-colors",
1885
+ active ? "border-primary/50 bg-primary/10 text-foreground" : "border-border text-muted-foreground hover:text-foreground hover:bg-accent"
1886
+ ),
1887
+ children: [
1888
+ /* @__PURE__ */ jsx6(ChipIcon, { className: "h-3 w-3" }),
1889
+ info.labelPlural
1890
+ ]
1891
+ },
1892
+ t
1893
+ );
1894
+ }) }),
1895
+ browseToken && BrowseOverride ? (
1896
+ // The token's canonical host picker (e.g. the stored-files browser)
1897
+ // owns per-token browsing when registered — never a plain list twin.
1898
+ /* @__PURE__ */ jsx6(
1899
+ BrowseOverride,
1900
+ {
1901
+ open: true,
1902
+ onOpenChange: (open) => {
1903
+ if (!open) setBrowseToken(null);
1904
+ },
1905
+ token: browseToken,
1906
+ orgId: orgId ?? null,
1907
+ attachedIds: new Set(
1908
+ [...attachedKeys].filter((k) => k.startsWith(`${browseToken}:`)).map((k) => k.slice(browseToken.length + 1))
1909
+ ),
1910
+ onAttach: (id, title) => onAttach(browseToken, id, title),
1911
+ onDetach: (id) => onDetach(browseToken, id)
1912
+ }
1913
+ )
1914
+ ) : browseToken ? /* @__PURE__ */ jsx6("div", { className: "flex min-h-0 flex-1 flex-col", children: /* @__PURE__ */ jsx6(
1915
+ AssociationCandidateBody,
1916
+ {
1917
+ token: browseToken,
1918
+ enabled: true,
1919
+ orgId,
1920
+ onClose: () => setBrowseToken(null),
1921
+ attachedIds: new Set(
1922
+ [...attachedKeys].filter((k) => k.startsWith(`${browseToken}:`)).map((k) => k.slice(browseToken.length + 1))
1923
+ ),
1924
+ onAttach: (id, title) => onAttach(browseToken, id, title),
1925
+ onDetach: (id) => onDetach(browseToken, id)
1926
+ }
1927
+ ) }) : /* @__PURE__ */ jsx6("div", { className: "-mx-1 min-h-0 flex-1 overflow-y-auto px-1", children: results.length === 0 ? /* @__PURE__ */ jsx6("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__ */ jsxs4("div", { className: "space-y-2", children: [
1928
+ isRecents && /* @__PURE__ */ jsx6("p", { className: "px-1 text-[10px] font-medium uppercase tracking-wider text-muted-foreground/60", children: "Recent" }),
1929
+ [...groups.entries()].map(([token, items]) => {
1930
+ const info = store.registry.getEntityInfo(token);
1931
+ const GroupIcon = info.Icon ?? DefaultEntityIcon;
1932
+ return /* @__PURE__ */ jsxs4("div", { className: "space-y-0.5", children: [
1933
+ /* @__PURE__ */ jsxs4("p", { className: "flex items-center gap-1 px-1 text-[10px] font-medium uppercase tracking-wider text-muted-foreground/70", children: [
1934
+ /* @__PURE__ */ jsx6(GroupIcon, { className: "h-3 w-3" }),
1935
+ info.labelPlural
1936
+ ] }),
1937
+ /* @__PURE__ */ jsx6("ul", { className: "space-y-0.5", children: items.map((c) => {
1938
+ const key = attachedKey(c.token, c.id);
1939
+ const attached = attachedKeys.has(key);
1940
+ const busy = busyKey === key;
1941
+ return /* @__PURE__ */ jsxs4(
1942
+ "li",
1943
+ {
1944
+ className: cn3(
1945
+ "group/entity-ref group flex items-center gap-1 rounded-md pr-1.5 transition-colors",
1946
+ "hover:bg-accent",
1947
+ attached && "bg-accent/40"
1948
+ ),
1949
+ children: [
1950
+ /* @__PURE__ */ jsxs4(
1951
+ "button",
1952
+ {
1953
+ type: "button",
1954
+ disabled: busy,
1955
+ onClick: () => toggle(c),
1956
+ title: attached ? `Detach "${c.title}"` : `Attach "${c.title}"`,
1957
+ className: cn3(
1958
+ "flex min-w-0 flex-1 items-center gap-2 rounded-md px-2.5 py-1.5 text-left text-sm",
1959
+ "disabled:opacity-50"
1960
+ ),
1961
+ children: [
1962
+ /* @__PURE__ */ jsx6("span", { className: "min-w-0 flex-1 truncate text-foreground", children: c.title }),
1963
+ /* @__PURE__ */ jsx6(
1964
+ "span",
1965
+ {
1966
+ className: cn3(
1967
+ "flex h-5 w-5 shrink-0 items-center justify-center rounded-full",
1968
+ attached ? "bg-primary text-primary-foreground" : "border border-border text-muted-foreground group-hover:border-primary/60"
1969
+ ),
1970
+ children: busy ? /* @__PURE__ */ jsx6(SpinnerIcon, { className: "h-3 w-3 animate-spin" }) : attached ? /* @__PURE__ */ jsx6(CheckIcon, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx6(PlusIcon, { className: "h-3 w-3" })
1971
+ }
1972
+ )
1973
+ ]
1974
+ }
1975
+ ),
1976
+ DoorControls ? /* @__PURE__ */ jsx6(
1977
+ DoorControls,
1978
+ {
1979
+ token: c.token,
1980
+ id: c.id,
1981
+ name: c.title
1982
+ }
1983
+ ) : null
1984
+ ]
1985
+ },
1986
+ key
1987
+ );
1988
+ }) })
1989
+ ] }, token);
1990
+ })
1991
+ ] }) })
1992
+ ] });
1993
+ }
1994
+
1995
+ // src/react/components/AttachedItemsSheet.tsx
1996
+ import { useEffect as useEffect7, useState as useState8 } from "react";
1997
+ import { cn as cn5 } from "@ai-matrx/design-system";
1998
+
1999
+ // src/react/components/entityDoors.tsx
2000
+ import { cn as cn4 } from "@ai-matrx/design-system";
2001
+ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
2002
+ function DoorRef(props) {
2003
+ const { onOpen, ...refProps } = props;
2004
+ const { entityDoors } = useAssociationsUiPorts();
2005
+ const store = useAssociationsStore();
2006
+ const HostRef = entityDoors?.EntityRef;
2007
+ if (HostRef) return /* @__PURE__ */ jsx7(HostRef, { ...refProps });
2008
+ const name = refProps.name ?? null;
2009
+ const href = store.registry.tryGetEntityInfo(refProps.token)?.hrefFor?.(refProps.id) ?? null;
2010
+ if (onOpen) {
2011
+ return /* @__PURE__ */ jsx7(
2012
+ "button",
2013
+ {
2014
+ type: "button",
2015
+ onClick: onOpen,
2016
+ className: cn4(
2017
+ "min-w-0 truncate text-left underline-offset-2 hover:underline",
2018
+ refProps.className
2019
+ ),
2020
+ children: name
2021
+ }
2022
+ );
2023
+ }
2024
+ if (href) {
2025
+ return /* @__PURE__ */ jsxs5(
2026
+ "a",
2027
+ {
2028
+ href,
2029
+ target: refProps.openInNewTab === false ? void 0 : "_blank",
2030
+ rel: "noopener noreferrer",
2031
+ className: cn4(
2032
+ "inline-flex min-w-0 items-center gap-1 truncate underline-offset-2 hover:underline",
2033
+ refProps.className
2034
+ ),
2035
+ children: [
2036
+ /* @__PURE__ */ jsx7("span", { className: "min-w-0 truncate", children: name }),
2037
+ /* @__PURE__ */ jsx7(ExternalLinkIcon, { className: "h-3 w-3 shrink-0 opacity-0 transition-opacity group-hover:opacity-60" })
2038
+ ]
2039
+ }
2040
+ );
2041
+ }
2042
+ return /* @__PURE__ */ jsx7("span", { className: cn4("min-w-0 truncate", refProps.className), children: name });
2043
+ }
2044
+ function UnresolvedRef({ token, id }) {
2045
+ const { entityDoors } = useAssociationsUiPorts();
2046
+ const HostUnresolved = entityDoors?.UnresolvedRef;
2047
+ if (HostUnresolved) return /* @__PURE__ */ jsx7(HostUnresolved, { token, id });
2048
+ return /* @__PURE__ */ jsx7(
2049
+ "span",
2050
+ {
2051
+ className: "inline-flex items-center rounded border border-border/60 bg-muted/40 px-1.5 py-0.5 text-[11px] text-muted-foreground",
2052
+ title: `This ${token} could not be read \u2014 it may be deleted or not shared with you.`,
2053
+ children: "Unavailable"
2054
+ }
2055
+ );
2056
+ }
2057
+
2058
+ // src/react/components/AttachedItemsSheet.tsx
2059
+ import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
2060
+ function AttachedItemsSheet(props) {
2061
+ const store = useAssociationsStore();
2062
+ const info = store.registry.getEntityInfo(props.token);
2063
+ const Icon = info.Icon ?? DefaultEntityIcon;
2064
+ const title = info.labelPlural;
2065
+ const containerName = props.container?.label ?? props.containerLabel;
2066
+ const subtitle = props.container ? /* @__PURE__ */ jsxs6("span", { className: "inline-flex min-w-0 max-w-full items-center gap-1", children: [
2067
+ "Attached to",
2068
+ /* @__PURE__ */ jsx8(
2069
+ DoorRef,
2070
+ {
2071
+ token: props.container.type,
2072
+ id: props.container.id,
2073
+ name: containerName ?? null,
2074
+ alwaysShowActions: true
2075
+ }
2076
+ )
2077
+ ] }) : props.containerLabel ? `Attached to ${props.containerLabel}` : "Attached items";
2078
+ if (!props.open) return null;
2079
+ return /* @__PURE__ */ jsx8(
2080
+ AssociationWindow,
2081
+ {
2082
+ open: props.open,
2083
+ onClose: () => props.onOpenChange(false),
2084
+ scopeId: `attached:${props.token}`,
2085
+ title,
2086
+ icon: /* @__PURE__ */ jsx8(Icon, { className: "size-3.5 text-primary" }),
2087
+ subtitle,
2088
+ children: /* @__PURE__ */ jsx8(
2089
+ AttachedItemsBody,
2090
+ {
2091
+ token: props.token,
2092
+ enabled: props.open,
2093
+ links: props.links,
2094
+ ...props.onAdd ? { onAdd: props.onAdd } : {},
2095
+ onDetach: props.onDetach
2096
+ }
2097
+ )
2098
+ }
2099
+ );
2100
+ }
2101
+ function AttachedItemsBody({
2102
+ token,
2103
+ enabled,
2104
+ links,
2105
+ onAdd,
2106
+ onDetach
2107
+ }) {
2108
+ const store = useAssociationsStore();
2109
+ const notifier = useNotifier();
2110
+ const info = store.registry.getEntityInfo(token);
2111
+ const Icon = info.Icon ?? DefaultEntityIcon;
2112
+ const [titles, setTitles] = useState8(null);
2113
+ const [titleError, setTitleError] = useState8(null);
2114
+ const [busyId, setBusyId] = useState8(null);
2115
+ const idKey = links.map((l) => l.resourceId).sort().join(",");
2116
+ useEffect7(() => {
2117
+ if (!enabled) return;
2118
+ const ids = idKey ? idKey.split(",") : [];
2119
+ if (ids.length === 0) {
2120
+ setTitles(/* @__PURE__ */ new Map());
2121
+ return;
2122
+ }
2123
+ let cancelled = false;
2124
+ void (async () => {
2125
+ try {
2126
+ const resolved = await store.titles.fetch(token, ids);
2127
+ if (!cancelled) {
2128
+ setTitles(resolved);
2129
+ setTitleError(null);
2130
+ }
2131
+ } catch (cause) {
2132
+ if (cancelled) return;
2133
+ store.errorSink({
2134
+ code: "title_resolution_failed",
2135
+ message: "[AttachedItemsSheet] title resolution failed",
2136
+ context: { token, detail: cause }
2137
+ });
2138
+ setTitles(/* @__PURE__ */ new Map());
2139
+ setTitleError(
2140
+ cause instanceof Error ? cause.message : "Could not resolve names"
2141
+ );
2142
+ }
2143
+ })();
2144
+ return () => {
2145
+ cancelled = true;
2146
+ };
2147
+ }, [token, idKey, enabled, store]);
2148
+ const detach = async (resourceId) => {
2149
+ if (busyId) return;
2150
+ setBusyId(resourceId);
2151
+ try {
2152
+ const res = await onDetach(resourceId);
2153
+ if (!res.ok) {
2154
+ notifier.error(`Couldn't detach${res.error ? `: ${res.error}` : ""}`);
2155
+ }
2156
+ } finally {
2157
+ setBusyId(null);
2158
+ }
2159
+ };
2160
+ return /* @__PURE__ */ jsxs6(Fragment2, { children: [
2161
+ titleError ? /* @__PURE__ */ jsxs6("div", { className: "mb-2 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-[12px] text-destructive", children: [
2162
+ "Showing names recorded at attach time \u2014 they may be out of date.",
2163
+ " ",
2164
+ titleError
2165
+ ] }) : null,
2166
+ /* @__PURE__ */ jsx8("div", { className: "flex-1 min-h-0 overflow-y-auto -mx-1 px-1", children: links.length === 0 ? /* @__PURE__ */ jsx8("div", { className: "flex items-center justify-center py-8 text-[13px] text-muted-foreground", children: "Nothing attached yet." }) : titles === null ? /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-center gap-2 py-8 text-[13px] text-muted-foreground", children: [
2167
+ /* @__PURE__ */ jsx8(SpinnerIcon, { className: "h-4 w-4 animate-spin" }),
2168
+ "Loading\u2026"
2169
+ ] }) : /* @__PURE__ */ jsx8("ul", { className: "space-y-0.5", children: links.map((link) => {
2170
+ const resolved = titles.get(link.resourceId);
2171
+ const missing = !titles.has(link.resourceId);
2172
+ const name = resolved ?? link.label ?? (missing ? null : "Untitled");
2173
+ const busy = busyId === link.resourceId;
2174
+ return /* @__PURE__ */ jsxs6(
2175
+ "li",
2176
+ {
2177
+ className: "group/entity-ref group flex items-center gap-2 rounded-md px-2.5 py-2 text-sm transition-colors hover:bg-accent",
2178
+ children: [
2179
+ /* @__PURE__ */ jsx8(Icon, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
2180
+ /* @__PURE__ */ jsx8("span", { className: "flex-1 min-w-0 truncate", children: name === null ? (
2181
+ // The X beside the row stays the one-click detach.
2182
+ /* @__PURE__ */ jsx8(UnresolvedRef, { token, id: link.resourceId })
2183
+ ) : /* @__PURE__ */ jsx8(
2184
+ DoorRef,
2185
+ {
2186
+ token,
2187
+ id: link.resourceId,
2188
+ name,
2189
+ showIcon: false,
2190
+ className: "text-foreground"
2191
+ }
2192
+ ) }),
2193
+ /* @__PURE__ */ jsx8(
2194
+ "button",
2195
+ {
2196
+ type: "button",
2197
+ disabled: busy,
2198
+ onClick: () => void detach(link.resourceId),
2199
+ title: "Detach",
2200
+ className: cn5(
2201
+ "flex h-6 w-6 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors",
2202
+ "hover:bg-destructive/10 hover:text-destructive disabled:opacity-50"
2203
+ ),
2204
+ children: busy ? /* @__PURE__ */ jsx8(SpinnerIcon, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsx8(XIcon, { className: "h-3.5 w-3.5" })
2205
+ }
2206
+ )
2207
+ ]
2208
+ },
2209
+ link.edgeId
2210
+ );
2211
+ }) }) }),
2212
+ onAdd ? /* @__PURE__ */ jsxs6(
2213
+ "button",
2214
+ {
2215
+ type: "button",
2216
+ onClick: onAdd,
2217
+ className: "mt-2 flex w-full items-center justify-center gap-1.5 rounded-md border border-border px-3 py-2 text-sm text-muted-foreground transition-colors hover:border-primary/60 hover:text-foreground",
2218
+ children: [
2219
+ /* @__PURE__ */ jsx8(PlusIcon, { className: "h-4 w-4" }),
2220
+ "Attach ",
2221
+ info.labelPlural.toLowerCase()
2222
+ ]
2223
+ }
2224
+ ) : null
2225
+ ] });
2226
+ }
2227
+
2228
+ // src/react/components/AssociationCard.tsx
2229
+ import { useState as useState9 } from "react";
2230
+ import { cn as cn6 } from "@ai-matrx/design-system";
2231
+
2232
+ // src/react/contentRoles.ts
2233
+ var CONTENT_ROLES = [
2234
+ {
2235
+ id: "utility",
2236
+ title: "Utilities",
2237
+ tagline: "The agents and tools that act on your knowledge.",
2238
+ accentText: "text-violet-600 dark:text-violet-400",
2239
+ accentBg: "bg-violet-500/10",
2240
+ accentBar: "bg-violet-500"
2241
+ },
2242
+ {
2243
+ id: "source",
2244
+ title: "Sources",
2245
+ tagline: "Incoming sources of truth (Knowledge In).",
2246
+ accentText: "text-sky-600 dark:text-sky-400",
2247
+ accentBg: "bg-sky-500/10",
2248
+ accentBar: "bg-sky-500"
2249
+ },
2250
+ {
2251
+ id: "destination",
2252
+ title: "Outputs",
2253
+ tagline: "Knowledge your team produces.",
2254
+ accentText: "text-emerald-600 dark:text-emerald-400",
2255
+ accentBg: "bg-emerald-500/10",
2256
+ accentBar: "bg-emerald-500"
2257
+ },
2258
+ {
2259
+ id: "hybrid",
2260
+ title: "Sources & Outputs",
2261
+ tagline: "Read from and written to.",
2262
+ accentText: "text-teal-600 dark:text-teal-400",
2263
+ accentBg: "bg-teal-500/10",
2264
+ accentBar: "bg-gradient-to-r from-sky-500 to-emerald-500"
2265
+ },
2266
+ {
2267
+ id: "container",
2268
+ title: "Workspaces",
2269
+ tagline: "How work is organized.",
2270
+ accentText: "text-amber-600 dark:text-amber-400",
2271
+ accentBg: "bg-amber-500/10",
2272
+ accentBar: "bg-amber-500"
2273
+ }
2274
+ ];
2275
+ function getContentRoleMeta(role) {
2276
+ return CONTENT_ROLES.find((r) => r.id === role) ?? CONTENT_ROLES[2];
2277
+ }
2278
+
2279
+ // src/react/components/AssociationCard.tsx
2280
+ import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
2281
+ function AssociationCard({
2282
+ token,
2283
+ container: containerProp,
2284
+ className
2285
+ }) {
2286
+ const store = useAssociationsStore();
2287
+ const fromCtx = usePrimaryEntity();
2288
+ const container = containerProp ?? fromCtx;
2289
+ const [open, setOpen] = useState9(false);
2290
+ const info = store.registry.getEntityInfo(token);
2291
+ const role = getContentRoleMeta(info.contentRole);
2292
+ const Icon = info.Icon ?? DefaultEntityIcon;
2293
+ const [listOpen, setListOpen] = useState9(false);
2294
+ const { status, countFor, attachedIdsFor, linksFor, attach, detach } = useContainerLinks({
2295
+ containerType: container?.type ?? "organization",
2296
+ containerId: container?.id ?? null,
2297
+ orgId: container?.orgId ?? null
2298
+ });
2299
+ if (!container) {
2300
+ store.errorSink({
2301
+ code: "association_card_no_container",
2302
+ message: `[AssociationCard] no primary entity for token "${token}" \u2014 wrap in <PrimaryEntityProvider> or pass a container prop`,
2303
+ context: { token }
2304
+ });
2305
+ return null;
2306
+ }
2307
+ const count = countFor(token);
2308
+ const loading = status === "loading" || status === "idle";
2309
+ const canAttach = info.canListCandidates;
2310
+ const canDrillIn = count > 0;
2311
+ return /* @__PURE__ */ jsxs7(Fragment3, { children: [
2312
+ /* @__PURE__ */ jsxs7(
2313
+ "div",
2314
+ {
2315
+ className: cn6(
2316
+ "group relative flex items-center gap-3 overflow-hidden rounded-xl border border-border bg-card transition-all hover:border-primary/30 hover:bg-accent/40",
2317
+ className
2318
+ ),
2319
+ children: [
2320
+ /* @__PURE__ */ jsx9(
2321
+ "span",
2322
+ {
2323
+ className: cn6(
2324
+ "absolute inset-x-0 top-0 h-0.5 opacity-60",
2325
+ role.accentBar
2326
+ )
2327
+ }
2328
+ ),
2329
+ /* @__PURE__ */ jsxs7(
2330
+ "button",
2331
+ {
2332
+ type: "button",
2333
+ disabled: !canDrillIn,
2334
+ onClick: () => setListOpen(true),
2335
+ title: canDrillIn ? `View the ${info.labelPlural.toLowerCase()} attached to ${container.label ?? "this"}` : void 0,
2336
+ className: cn6(
2337
+ "flex min-w-0 flex-1 items-center gap-3 px-3 py-2.5 text-left",
2338
+ canDrillIn ? "cursor-pointer" : "cursor-default"
2339
+ ),
2340
+ children: [
2341
+ /* @__PURE__ */ jsx9(
2342
+ "span",
2343
+ {
2344
+ className: cn6(
2345
+ "flex h-9 w-9 shrink-0 items-center justify-center rounded-lg",
2346
+ role.accentBg,
2347
+ role.accentText
2348
+ ),
2349
+ children: /* @__PURE__ */ jsx9(Icon, { className: "h-4 w-4" })
2350
+ }
2351
+ ),
2352
+ /* @__PURE__ */ jsxs7("span", { className: "min-w-0 flex-1", children: [
2353
+ /* @__PURE__ */ jsx9("span", { className: "block truncate text-sm font-medium text-foreground", children: info.labelPlural }),
2354
+ /* @__PURE__ */ jsx9("span", { className: "block text-[11px] text-muted-foreground tabular-nums", children: loading ? /* @__PURE__ */ jsxs7("span", { className: "inline-flex items-center gap-1", children: [
2355
+ /* @__PURE__ */ jsx9(SpinnerIcon, { className: "h-3 w-3 animate-spin" }),
2356
+ "Loading\u2026"
2357
+ ] }) : `${count} attached` })
2358
+ ] })
2359
+ ]
2360
+ }
2361
+ ),
2362
+ canAttach && /* @__PURE__ */ jsx9(
2363
+ "button",
2364
+ {
2365
+ type: "button",
2366
+ onClick: () => setOpen(true),
2367
+ "aria-label": `Attach ${info.labelPlural.toLowerCase()}`,
2368
+ title: `Attach ${info.labelPlural.toLowerCase()}`,
2369
+ className: "mr-2 flex h-11 w-11 shrink-0 items-center justify-center rounded-md border border-border text-muted-foreground transition-colors hover:border-primary/60 hover:text-foreground lg:mr-3 lg:h-7 lg:w-7",
2370
+ children: /* @__PURE__ */ jsx9(PlusIcon, { className: "h-4 w-4" })
2371
+ }
2372
+ )
2373
+ ]
2374
+ }
2375
+ ),
2376
+ listOpen && /* @__PURE__ */ jsx9(
2377
+ AttachedItemsSheet,
2378
+ {
2379
+ open: listOpen,
2380
+ onOpenChange: setListOpen,
2381
+ token,
2382
+ ...container.label !== void 0 ? { containerLabel: container.label } : {},
2383
+ container,
2384
+ links: linksFor(token),
2385
+ ...canAttach ? {
2386
+ onAdd: () => {
2387
+ setListOpen(false);
2388
+ setOpen(true);
2389
+ }
2390
+ } : {},
2391
+ onDetach: (resourceId) => detach(token, resourceId)
2392
+ }
2393
+ ),
2394
+ canAttach && /* @__PURE__ */ jsx9(
2395
+ AssociationPicker,
2396
+ {
2397
+ open,
2398
+ onOpenChange: setOpen,
2399
+ token,
2400
+ ...container.label !== void 0 ? { containerLabel: container.label } : {},
2401
+ orgId: container.orgId ?? null,
2402
+ attachedIds: attachedIdsFor(token),
2403
+ onAttach: (resourceId, title) => attach(token, resourceId, title),
2404
+ onDetach: (resourceId) => detach(token, resourceId)
2405
+ }
2406
+ )
2407
+ ] });
2408
+ }
2409
+
2410
+ // src/react/components/AssociationCardGrid.tsx
2411
+ import { cn as cn7 } from "@ai-matrx/design-system";
2412
+ import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
2413
+ function AssociationCardGrid({
2414
+ tokens,
2415
+ excludeTokens,
2416
+ className
2417
+ }) {
2418
+ const store = useAssociationsStore();
2419
+ const excluded = new Set(excludeTokens ?? []);
2420
+ const list = (tokens ?? store.registry.curatedTokens()).filter(
2421
+ (token) => !excluded.has(token)
2422
+ );
2423
+ return /* @__PURE__ */ jsx10("div", { className: cn7("space-y-5", className), children: CONTENT_ROLES.map((role) => {
2424
+ const inRole = list.filter(
2425
+ (token) => store.registry.getEntityInfo(token).contentRole === role.id
2426
+ );
2427
+ if (inRole.length === 0) return null;
2428
+ return /* @__PURE__ */ jsxs8("section", { children: [
2429
+ /* @__PURE__ */ jsxs8("div", { className: "mb-2.5 flex items-baseline gap-3 pl-1.5", children: [
2430
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2", children: [
2431
+ /* @__PURE__ */ jsx10(
2432
+ "span",
2433
+ {
2434
+ className: cn7("h-2.5 w-2.5 rounded-full", role.accentBar)
2435
+ }
2436
+ ),
2437
+ /* @__PURE__ */ jsx10("h3", { className: "text-sm font-semibold text-foreground", children: role.title })
2438
+ ] }),
2439
+ /* @__PURE__ */ jsx10("p", { className: "hidden text-xs text-muted-foreground sm:block", children: role.tagline })
2440
+ ] }),
2441
+ /* @__PURE__ */ jsx10("div", { className: "grid grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-3", children: inRole.map((token) => /* @__PURE__ */ jsx10(AssociationCard, { token }, token)) })
2442
+ ] }, role.id);
2443
+ }) });
2444
+ }
2445
+
2446
+ // src/react/components/AssociationList.tsx
2447
+ import { useState as useState10 } from "react";
2448
+ import { cn as cn8, Skeleton } from "@ai-matrx/design-system";
2449
+ import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
2450
+ function useContainerLinksAdapter(container, tokens) {
2451
+ const store = useAssociationsStore();
2452
+ const links = useContainerLinks({
2453
+ containerType: container?.type ?? "organization",
2454
+ containerId: container?.id ?? null,
2455
+ orgId: container?.orgId ?? null
2456
+ });
2457
+ const rows = container ? (tokens ?? store.registry.curatedTokens()).flatMap(
2458
+ (token) => links.linksFor(token).map((l) => ({
2459
+ key: l.edgeId,
2460
+ token: l.token,
2461
+ resourceId: l.resourceId,
2462
+ label: l.label,
2463
+ role: l.role,
2464
+ removable: true
2465
+ }))
2466
+ ) : [];
2467
+ return {
2468
+ status: links.status,
2469
+ error: links.error,
2470
+ reload: links.reload,
2471
+ rows,
2472
+ attach: async (token, id, title) => links.attach(token, id, title),
2473
+ detach: async (token, id, role) => links.detach(token, id, role)
2474
+ };
2475
+ }
2476
+ function AssociationList(props) {
2477
+ const store = useAssociationsStore();
2478
+ const ctxEntity = usePrimaryEntity();
2479
+ const container = props.container ?? ctxEntity ?? null;
2480
+ const defaultAdapter = useContainerLinksAdapter(
2481
+ props.adapter ? null : container,
2482
+ props.tokens
2483
+ );
2484
+ const adapter = props.adapter ?? defaultAdapter;
2485
+ const variant = props.variant ?? "full";
2486
+ const tokenFilter = props.tokens ?? null;
2487
+ const [pickerToken, setPickerToken] = useState10(null);
2488
+ const [showUniversal, setShowUniversal] = useState10(false);
2489
+ const [removingKeys, setRemovingKeys] = useState10(/* @__PURE__ */ new Set());
2490
+ const rows = tokenFilter ? adapter.rows.filter((r) => tokenFilter.includes(r.token)) : adapter.rows;
2491
+ const visibleRows = rows.filter((r) => !removingKeys.has(r.key));
2492
+ const { titleFor } = useEntityTitles(
2493
+ visibleRows.map((r) => ({
2494
+ token: r.token,
2495
+ id: r.resourceId,
2496
+ label: r.label
2497
+ }))
2498
+ );
2499
+ const attachedKeys = new Set(
2500
+ rows.map((r) => attachedKey(r.token, r.resourceId))
2501
+ );
2502
+ const handleDetach = async (row) => {
2503
+ setRemovingKeys((prev) => new Set(prev).add(row.key));
2504
+ const info = store.registry.tryGetEntityInfo(row.token);
2505
+ const res = info ? await adapter.detach(info.token, row.resourceId, row.role ?? void 0) : { ok: false };
2506
+ setRemovingKeys((prev) => {
2507
+ const next = new Set(prev);
2508
+ next.delete(row.key);
2509
+ return next;
2510
+ });
2511
+ return res;
2512
+ };
2513
+ const openRow = (row) => {
2514
+ if (props.openEntity) {
2515
+ props.openEntity(row.token, row.resourceId);
2516
+ return;
2517
+ }
2518
+ const info = store.registry.tryGetEntityInfo(row.token);
2519
+ const href = info?.hrefFor?.(row.resourceId);
2520
+ if (href && typeof window !== "undefined") {
2521
+ window.open(href, "_blank", "noopener,noreferrer");
2522
+ }
2523
+ };
2524
+ const grouped = groupRows(store, visibleRows);
2525
+ const isLoading = adapter.status === "loading" || adapter.status === "idle";
2526
+ const attachableTokens = tokenFilter ?? store.registry.curatedTokens();
2527
+ return /* @__PURE__ */ jsxs9("div", { className: cn8("flex flex-col gap-2 text-foreground", props.className), children: [
2528
+ /* @__PURE__ */ jsxs9("div", { className: "flex items-center justify-between gap-2 px-1", children: [
2529
+ /* @__PURE__ */ jsxs9("h3", { className: "flex items-center gap-1.5 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: [
2530
+ /* @__PURE__ */ jsx11(Link2Icon, { className: "h-3.5 w-3.5" }),
2531
+ "Resources",
2532
+ visibleRows.length > 0 && /* @__PURE__ */ jsx11("span", { className: "rounded bg-muted px-1 text-[10px] font-medium text-muted-foreground", children: visibleRows.length })
2533
+ ] }),
2534
+ /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-1.5", children: [
2535
+ /* @__PURE__ */ jsxs9(
2536
+ "button",
2537
+ {
2538
+ type: "button",
2539
+ onClick: () => setShowUniversal((v) => !v),
2540
+ className: cn8(
2541
+ "inline-flex items-center gap-1 rounded-md border border-border px-1.5 py-0.5 text-[11px] transition-colors",
2542
+ showUniversal ? "bg-accent text-foreground" : "text-muted-foreground hover:bg-accent hover:text-foreground"
2543
+ ),
2544
+ children: [
2545
+ /* @__PURE__ */ jsx11(PlusIcon, { className: "h-3 w-3" }),
2546
+ "Add"
2547
+ ]
2548
+ }
2549
+ ),
2550
+ /* @__PURE__ */ jsx11(
2551
+ "button",
2552
+ {
2553
+ type: "button",
2554
+ onClick: () => void adapter.reload(),
2555
+ title: "Refresh",
2556
+ className: "text-muted-foreground/60 transition-colors hover:text-foreground",
2557
+ children: /* @__PURE__ */ jsx11(
2558
+ RefreshIcon,
2559
+ {
2560
+ className: cn8(
2561
+ "h-3 w-3",
2562
+ adapter.status === "loading" && "animate-spin"
2563
+ )
2564
+ }
2565
+ )
2566
+ }
2567
+ )
2568
+ ] })
2569
+ ] }),
2570
+ showUniversal && /* @__PURE__ */ jsx11("div", { className: "rounded-md border border-border/60 bg-muted/30 p-2 max-h-80 flex flex-col", children: /* @__PURE__ */ jsx11(
2571
+ UniversalAssociationPicker,
2572
+ {
2573
+ tokens: attachableTokens,
2574
+ orgId: container?.orgId ?? null,
2575
+ attachedKeys,
2576
+ onAttach: (token, id, title) => adapter.attach(token, id, title),
2577
+ onDetach: (token, id) => adapter.detach(token, id)
2578
+ }
2579
+ ) }),
2580
+ adapter.status === "error" && /* @__PURE__ */ jsxs9("div", { className: "flex items-center justify-between gap-2 rounded-md border border-destructive/40 bg-destructive/10 px-2.5 py-2", children: [
2581
+ /* @__PURE__ */ jsx11("span", { className: "truncate text-[11px] text-destructive", children: adapter.error || "Failed to load resources" }),
2582
+ /* @__PURE__ */ jsx11(
2583
+ "button",
2584
+ {
2585
+ type: "button",
2586
+ onClick: () => void adapter.reload(),
2587
+ className: "text-[11px] font-medium text-destructive underline hover:no-underline",
2588
+ children: "Retry"
2589
+ }
2590
+ )
2591
+ ] }),
2592
+ adapter.status !== "error" && isLoading && visibleRows.length === 0 && /* @__PURE__ */ jsxs9("div", { className: "space-y-1.5 px-1", children: [
2593
+ /* @__PURE__ */ jsx11(Skeleton, { className: "h-3 w-20" }),
2594
+ /* @__PURE__ */ jsx11(Skeleton, { className: "h-7 w-full rounded-md" }),
2595
+ /* @__PURE__ */ jsx11(Skeleton, { className: "h-7 w-3/4 rounded-md" })
2596
+ ] }),
2597
+ adapter.status === "ready" && visibleRows.length === 0 && /* @__PURE__ */ jsx11("p", { className: "px-1 py-1 text-[11px] text-muted-foreground", children: "Nothing attached yet \u2014 use Add to link anything." }),
2598
+ visibleRows.length > 0 && /* @__PURE__ */ jsx11("div", { className: "space-y-2.5", children: grouped.map(({ role, tokens: tokenGroups }) => {
2599
+ const roleMeta = getContentRoleMeta(role);
2600
+ return /* @__PURE__ */ jsxs9("div", { className: "space-y-1.5", children: [
2601
+ variant === "full" && /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-1.5 px-1", children: [
2602
+ /* @__PURE__ */ jsx11(
2603
+ "span",
2604
+ {
2605
+ className: cn8(
2606
+ "h-3 w-0.5 rounded-full",
2607
+ roleMeta.accentBar
2608
+ )
2609
+ }
2610
+ ),
2611
+ /* @__PURE__ */ jsx11(
2612
+ "p",
2613
+ {
2614
+ className: cn8(
2615
+ "text-[10px] font-semibold uppercase tracking-wider",
2616
+ roleMeta.accentText
2617
+ ),
2618
+ children: roleMeta.title
2619
+ }
2620
+ )
2621
+ ] }),
2622
+ tokenGroups.map(({ info, token, rows: tokenRows }) => {
2623
+ const TokenIcon = info?.Icon ?? null;
2624
+ return /* @__PURE__ */ jsxs9("div", { className: "space-y-0.5", children: [
2625
+ /* @__PURE__ */ jsxs9("div", { className: "flex items-center justify-between gap-1 px-1", children: [
2626
+ /* @__PURE__ */ jsxs9("p", { className: "flex items-center gap-1 text-[10px] font-medium text-muted-foreground/80", children: [
2627
+ TokenIcon ? /* @__PURE__ */ jsx11(TokenIcon, { className: "h-3 w-3" }) : null,
2628
+ info?.labelPlural ?? titleize(token),
2629
+ /* @__PURE__ */ jsx11("span", { className: "text-muted-foreground/50", children: tokenRows.length })
2630
+ ] }),
2631
+ /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-1", children: [
2632
+ props.renderSectionActions && info ? props.renderSectionActions(info.token) : null,
2633
+ info?.canListCandidates && /* @__PURE__ */ jsx11(
2634
+ "button",
2635
+ {
2636
+ type: "button",
2637
+ onClick: () => setPickerToken(info.token),
2638
+ title: `Add ${info.labelPlural}`,
2639
+ className: "rounded p-0.5 text-muted-foreground/60 transition-colors hover:bg-accent hover:text-foreground",
2640
+ children: /* @__PURE__ */ jsx11(PlusIcon, { className: "h-3 w-3" })
2641
+ }
2642
+ )
2643
+ ] })
2644
+ ] }),
2645
+ /* @__PURE__ */ jsx11("ul", { className: "space-y-0.5", children: tokenRows.map((row) => {
2646
+ const busy = removingKeys.has(row.key);
2647
+ const custom = props.renderRow?.(row, {
2648
+ title: titleFor({
2649
+ token: row.token,
2650
+ id: row.resourceId,
2651
+ label: row.label
2652
+ }),
2653
+ busy,
2654
+ onDetach: () => void handleDetach(row),
2655
+ onOpen: () => openRow(row)
2656
+ });
2657
+ if (custom != null) {
2658
+ return /* @__PURE__ */ jsx11("li", { children: custom }, row.key);
2659
+ }
2660
+ return /* @__PURE__ */ jsx11("li", { children: /* @__PURE__ */ jsxs9(
2661
+ "div",
2662
+ {
2663
+ className: cn8(
2664
+ "group flex items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors hover:bg-accent",
2665
+ busy && "opacity-50"
2666
+ ),
2667
+ children: [
2668
+ /* @__PURE__ */ jsx11(
2669
+ DoorRef,
2670
+ {
2671
+ token: row.token,
2672
+ id: row.resourceId,
2673
+ name: titleFor({
2674
+ token: row.token,
2675
+ id: row.resourceId,
2676
+ label: row.label
2677
+ }),
2678
+ showIcon: Boolean(TokenIcon),
2679
+ onOpen: props.openEntity ? () => openRow(row) : void 0,
2680
+ openInNewTab: !props.openEntity,
2681
+ className: "min-w-0 flex-1 text-foreground"
2682
+ }
2683
+ ),
2684
+ row.originRefs && row.originRefs.length > 0 ? /* @__PURE__ */ jsxs9("span", { className: "flex shrink-0 items-center gap-1 text-[10px] text-muted-foreground/60", children: [
2685
+ "via",
2686
+ row.originRefs.map((origin) => /* @__PURE__ */ jsx11(
2687
+ DoorRef,
2688
+ {
2689
+ token: origin.token,
2690
+ id: origin.id,
2691
+ name: origin.label,
2692
+ showIcon: false,
2693
+ openInNewTab: true,
2694
+ className: "max-w-24 text-[10px] text-muted-foreground underline-offset-2"
2695
+ },
2696
+ `${origin.token}:${origin.id}`
2697
+ ))
2698
+ ] }) : row.originNote ? /* @__PURE__ */ jsx11("span", { className: "shrink-0 text-[10px] text-muted-foreground/60", children: row.originNote }) : null,
2699
+ adapter.setPinned && info && row.removable && /* @__PURE__ */ jsx11(
2700
+ "button",
2701
+ {
2702
+ type: "button",
2703
+ onClick: () => void adapter.setPinned?.(
2704
+ info.token,
2705
+ row.resourceId,
2706
+ !(row.pinned ?? false)
2707
+ ),
2708
+ title: row.pinned ? "Unpin (stops staying in agent context)" : "Pin \u2014 always in agent context",
2709
+ "aria-label": row.pinned ? "Unpin" : "Pin",
2710
+ className: cn8(
2711
+ "shrink-0 rounded p-0.5 transition-colors hover:!text-foreground",
2712
+ row.pinned ? "text-primary" : "text-muted-foreground/0 group-hover:text-muted-foreground"
2713
+ ),
2714
+ children: /* @__PURE__ */ jsx11(
2715
+ PinIcon,
2716
+ {
2717
+ className: cn8(
2718
+ "h-3 w-3",
2719
+ row.pinned && "fill-primary/20"
2720
+ )
2721
+ }
2722
+ )
2723
+ }
2724
+ ),
2725
+ row.removable && /* @__PURE__ */ jsx11(
2726
+ "button",
2727
+ {
2728
+ type: "button",
2729
+ disabled: busy,
2730
+ onClick: () => void handleDetach(row),
2731
+ title: "Detach",
2732
+ "aria-label": "Detach",
2733
+ className: "shrink-0 rounded p-0.5 text-muted-foreground/0 transition-colors group-hover:text-muted-foreground hover:!text-destructive disabled:opacity-50",
2734
+ children: busy ? /* @__PURE__ */ jsx11(SpinnerIcon, { className: "h-3 w-3 animate-spin" }) : /* @__PURE__ */ jsx11(XIcon, { className: "h-3 w-3" })
2735
+ }
2736
+ )
2737
+ ]
2738
+ }
2739
+ ) }, row.key);
2740
+ }) })
2741
+ ] }, token);
2742
+ })
2743
+ ] }, role);
2744
+ }) }),
2745
+ pickerToken && /* @__PURE__ */ jsx11(
2746
+ AssociationPicker,
2747
+ {
2748
+ open: true,
2749
+ onOpenChange: (open) => {
2750
+ if (!open) setPickerToken(null);
2751
+ },
2752
+ token: pickerToken,
2753
+ ...container?.label !== void 0 ? { containerLabel: container.label } : {},
2754
+ orgId: container?.orgId ?? null,
2755
+ attachedIds: new Set(
2756
+ rows.filter((r) => r.token === pickerToken).map((r) => r.resourceId)
2757
+ ),
2758
+ onAttach: (id, title) => adapter.attach(pickerToken, id, title),
2759
+ onDetach: (id) => adapter.detach(pickerToken, id)
2760
+ }
2761
+ )
2762
+ ] });
2763
+ }
2764
+ function groupRows(store, rows) {
2765
+ const byToken = /* @__PURE__ */ new Map();
2766
+ for (const r of rows) {
2767
+ const list = byToken.get(r.token) ?? [];
2768
+ list.push(r);
2769
+ byToken.set(r.token, list);
2770
+ }
2771
+ const roleBuckets = /* @__PURE__ */ new Map();
2772
+ for (const [token, tokenRows] of byToken) {
2773
+ const info = store.registry.tryGetEntityInfo(token);
2774
+ const role = info?.contentRole ?? "destination";
2775
+ tokenRows.sort(
2776
+ (a, b) => Number(b.pinned ?? false) - Number(a.pinned ?? false)
2777
+ );
2778
+ const groups = roleBuckets.get(role) ?? [];
2779
+ groups.push({ token, info, rows: tokenRows });
2780
+ roleBuckets.set(role, groups);
2781
+ }
2782
+ const out = [];
2783
+ for (const meta of CONTENT_ROLES) {
2784
+ const tokens = roleBuckets.get(meta.id);
2785
+ if (tokens && tokens.length > 0) {
2786
+ tokens.sort(
2787
+ (a, b) => (a.info?.labelPlural ?? a.token).localeCompare(
2788
+ b.info?.labelPlural ?? b.token
2789
+ )
2790
+ );
2791
+ out.push({ role: meta.id, tokens });
2792
+ }
2793
+ }
2794
+ return out;
2795
+ }
2796
+ function titleize(token) {
2797
+ return token.split("_").map((w) => w ? w[0].toUpperCase() + w.slice(1) : w).join(" ");
2798
+ }
2799
+
2800
+ // src/react/components/AssociationCaptureToolbar.tsx
2801
+ import { useRef as useRef6, useState as useState11 } from "react";
2802
+ import { Button, cn as cn9, Input as Input3 } from "@ai-matrx/design-system";
2803
+ import { Fragment as Fragment4, jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
2804
+ function AssociationCaptureToolbar({
2805
+ attach,
2806
+ uploadFolderPath,
2807
+ uploadVisibility = "personal",
2808
+ uploadLocationLabel,
2809
+ attachRetryLabel = '"Add file"',
2810
+ filePicker,
2811
+ showActions,
2812
+ showToolbar = true,
2813
+ extraActions,
2814
+ openCreatedDocument = true,
2815
+ className,
2816
+ children
2817
+ }) {
2818
+ const store = useAssociationsStore();
2819
+ const { capture } = useAssociationsUiPorts();
2820
+ const notifier = useNotifier();
2821
+ const fileInputRef = useRef6(null);
2822
+ const [isUploading, setIsUploading] = useState11(false);
2823
+ const [isDragOver, setIsDragOver] = useState11(false);
2824
+ const [isPicking, setIsPicking] = useState11(false);
2825
+ const [namingDoc, setNamingDoc] = useState11(false);
2826
+ const [docName, setDocName] = useState11("");
2827
+ const [creatingDoc, setCreatingDoc] = useState11(false);
2828
+ const actions = {
2829
+ upload: (showActions?.upload ?? true) && Boolean(capture?.requestUpload),
2830
+ addFile: (showActions?.addFile ?? true) && Boolean(capture?.openFilePicker),
2831
+ newDocument: (showActions?.newDocument ?? true) && Boolean(capture?.createDataTable)
2832
+ };
2833
+ const canDrop = Boolean(capture?.requestUpload);
2834
+ const uploadAndAttach = async (files) => {
2835
+ const requestUpload = capture?.requestUpload;
2836
+ if (files.length === 0 || !requestUpload) return;
2837
+ setIsUploading(true);
2838
+ const watchdog = setTimeout(() => {
2839
+ store.errorSink({
2840
+ code: "capture_upload_unresponsive",
2841
+ message: "[AssociationCaptureToolbar] upload pipeline unresponsive >60s",
2842
+ context: { files: files.map((f) => f.name) }
2843
+ });
2844
+ notifier.error(
2845
+ "The upload isn't responding \u2014 it may be stalled. Check your connection and retry."
2846
+ );
2847
+ }, 6e4);
2848
+ try {
2849
+ const result = await requestUpload({
2850
+ files,
2851
+ folderPath: uploadFolderPath,
2852
+ visibility: uploadVisibility
2853
+ });
2854
+ if (result.cancelled) {
2855
+ notifier.success("Upload cancelled \u2014 nothing was attached");
2856
+ return;
2857
+ }
2858
+ const aliasedIds = result.aliased.map((a) => a.existingFileId);
2859
+ const ids = [...result.uploaded, ...aliasedIds];
2860
+ let attached = 0;
2861
+ const unattached = [];
2862
+ let firstAttachError = null;
2863
+ for (const id of ids) {
2864
+ const res = await attach("file", id);
2865
+ if (res.ok) {
2866
+ attached += 1;
2867
+ } else {
2868
+ unattached.push(id);
2869
+ if (!firstAttachError && res.error) firstAttachError = res.error;
2870
+ }
2871
+ }
2872
+ if (attached > 0) {
2873
+ notifier.success(
2874
+ attached === 1 ? "Attached 1 file" : `Attached ${attached} files`
2875
+ );
2876
+ }
2877
+ if (unattached.length > 0) {
2878
+ notifier.error(
2879
+ `Uploaded ${unattached.length === 1 ? "1 file" : `${unattached.length} files`} to ${uploadLocationLabel} but couldn't attach ${unattached.length === 1 ? "it" : "them"} here \u2014 use ${attachRetryLabel} to retry` + (firstAttachError ? ` (${firstAttachError})` : "")
2880
+ );
2881
+ }
2882
+ if (result.failed.length > 0) {
2883
+ const first = result.failed[0];
2884
+ notifier.error(
2885
+ result.failed.length === 1 && first ? `Failed to upload ${first.name}: ${first.error}` : `Failed to upload ${result.failed.length} files`
2886
+ );
2887
+ }
2888
+ if (ids.length === 0 && result.failed.length === 0) {
2889
+ notifier.error("Nothing was uploaded or attached");
2890
+ }
2891
+ } catch (err) {
2892
+ store.errorSink({
2893
+ code: "capture_upload_failed",
2894
+ message: "[AssociationCaptureToolbar] upload flow failed",
2895
+ context: { detail: err }
2896
+ });
2897
+ notifier.error(
2898
+ err instanceof Error && err.message ? `Upload failed: ${err.message}` : "Upload failed unexpectedly"
2899
+ );
2900
+ } finally {
2901
+ clearTimeout(watchdog);
2902
+ setIsUploading(false);
2903
+ }
2904
+ };
2905
+ const handleFilesSelected = (e) => {
2906
+ const files = e.target.files ? Array.from(e.target.files) : [];
2907
+ e.target.value = "";
2908
+ void uploadAndAttach(files);
2909
+ };
2910
+ const handleDrop = (e) => {
2911
+ if (!canDrop) return;
2912
+ e.preventDefault();
2913
+ e.stopPropagation();
2914
+ setIsDragOver(false);
2915
+ const files = Array.from(e.dataTransfer?.files ?? []);
2916
+ if (files.length === 0) return;
2917
+ void uploadAndAttach(files);
2918
+ };
2919
+ const handlePickExisting = async () => {
2920
+ const openFilePicker = capture?.openFilePicker;
2921
+ if (isPicking || !openFilePicker) return;
2922
+ setIsPicking(true);
2923
+ try {
2924
+ const ids = await openFilePicker({
2925
+ multi: true,
2926
+ title: filePicker?.title ?? "Attach files",
2927
+ description: filePicker?.description ?? "Pick existing files from your cloud storage."
2928
+ });
2929
+ if (!ids || ids.length === 0) return;
2930
+ let attached = 0;
2931
+ let firstAttachError = null;
2932
+ for (const id of ids) {
2933
+ const res = await attach("file", id);
2934
+ if (res.ok) attached += 1;
2935
+ else if (!firstAttachError && res.error) firstAttachError = res.error;
2936
+ }
2937
+ if (attached > 0) {
2938
+ notifier.success(
2939
+ attached === 1 ? "Attached 1 file" : `Attached ${attached} files`
2940
+ );
2941
+ }
2942
+ if (attached < ids.length && firstAttachError) {
2943
+ notifier.error(`Couldn't attach: ${firstAttachError}`);
2944
+ }
2945
+ } catch (err) {
2946
+ store.errorSink({
2947
+ code: "capture_pick_failed",
2948
+ message: "[AssociationCaptureToolbar] attach-existing flow failed",
2949
+ context: { detail: err }
2950
+ });
2951
+ notifier.error("Couldn't attach the selected files");
2952
+ } finally {
2953
+ setIsPicking(false);
2954
+ }
2955
+ };
2956
+ const handleCreateDoc = async () => {
2957
+ const createDataTable = capture?.createDataTable;
2958
+ const name = docName.trim();
2959
+ if (!name || creatingDoc || !createDataTable) return;
2960
+ setCreatingDoc(true);
2961
+ try {
2962
+ const doc = await createDataTable({ name });
2963
+ if (!doc) {
2964
+ notifier.error("Couldn't create the document");
2965
+ return;
2966
+ }
2967
+ const res = await attach("udt_document", doc.id, { label: name });
2968
+ setNamingDoc(false);
2969
+ setDocName("");
2970
+ if (res.ok) {
2971
+ notifier.success("Document created");
2972
+ if (openCreatedDocument && typeof window !== "undefined") {
2973
+ const href = store.registry.tryGetEntityInfo("udt_document")?.hrefFor?.(doc.id);
2974
+ if (href) window.open(href, "_blank", "noopener,noreferrer");
2975
+ }
2976
+ } else {
2977
+ notifier.error(
2978
+ `Created "${name}" but couldn't attach it here \u2014 it's saved in your documents; retry from its library`
2979
+ );
2980
+ }
2981
+ } catch (err) {
2982
+ store.errorSink({
2983
+ code: "capture_create_document_failed",
2984
+ message: "[AssociationCaptureToolbar] create-document flow failed",
2985
+ context: { detail: err }
2986
+ });
2987
+ notifier.error("Couldn't create the document");
2988
+ } finally {
2989
+ setCreatingDoc(false);
2990
+ }
2991
+ };
2992
+ const anyBuiltIn = actions.upload || actions.addFile || actions.newDocument;
2993
+ return /* @__PURE__ */ jsxs10(
2994
+ "div",
2995
+ {
2996
+ className: cn9(
2997
+ "relative flex h-full min-h-0 flex-col",
2998
+ isDragOver && "ring-2 ring-inset ring-primary/50",
2999
+ className
3000
+ ),
3001
+ onDragOver: (e) => {
3002
+ if (!canDrop) return;
3003
+ if (!e.dataTransfer?.types.includes("Files")) return;
3004
+ e.preventDefault();
3005
+ e.stopPropagation();
3006
+ setIsDragOver(true);
3007
+ },
3008
+ onDragLeave: (e) => {
3009
+ if (e.currentTarget.contains(e.relatedTarget)) return;
3010
+ setIsDragOver(false);
3011
+ },
3012
+ onDrop: handleDrop,
3013
+ children: [
3014
+ isDragOver ? /* @__PURE__ */ jsx12("div", { className: "pointer-events-none absolute inset-0 z-10 grid place-items-center bg-background/70", children: /* @__PURE__ */ jsxs10("span", { className: "flex items-center gap-2 rounded-md border border-primary/40 bg-card px-3 py-1.5 text-xs font-medium text-foreground", children: [
3015
+ /* @__PURE__ */ jsx12(UploadIcon, { className: "size-3.5 text-primary" }),
3016
+ "Drop to upload & attach"
3017
+ ] }) }) : null,
3018
+ /* @__PURE__ */ jsx12(
3019
+ "input",
3020
+ {
3021
+ ref: fileInputRef,
3022
+ type: "file",
3023
+ multiple: true,
3024
+ className: "sr-only",
3025
+ onChange: handleFilesSelected
3026
+ }
3027
+ ),
3028
+ showToolbar && (anyBuiltIn || extraActions) ? /* @__PURE__ */ jsx12("div", { className: "flex flex-wrap items-center gap-0.5 border-b border-border/60 px-1.5 py-1", children: namingDoc ? /* @__PURE__ */ jsxs10("div", { className: "flex flex-1 items-center gap-1.5 py-0.5", children: [
3029
+ /* @__PURE__ */ jsx12(
3030
+ Input3,
3031
+ {
3032
+ autoFocus: true,
3033
+ value: docName,
3034
+ disabled: creatingDoc,
3035
+ placeholder: "Document name\u2026",
3036
+ onChange: (e) => setDocName(e.target.value),
3037
+ onKeyDown: (e) => {
3038
+ if (e.key === "Enter") void handleCreateDoc();
3039
+ if (e.key === "Escape") {
3040
+ setNamingDoc(false);
3041
+ setDocName("");
3042
+ }
3043
+ },
3044
+ className: "h-7 flex-1 text-base md:text-xs",
3045
+ style: { fontSize: 16 }
3046
+ }
3047
+ ),
3048
+ /* @__PURE__ */ jsx12(
3049
+ "button",
3050
+ {
3051
+ type: "button",
3052
+ disabled: creatingDoc || !docName.trim(),
3053
+ onClick: () => void handleCreateDoc(),
3054
+ className: "flex h-7 items-center rounded-md bg-primary px-2 text-[11px] font-medium text-primary-foreground disabled:opacity-50",
3055
+ children: creatingDoc ? /* @__PURE__ */ jsx12(SpinnerIcon, { className: "size-3.5 animate-spin" }) : "Create"
3056
+ }
3057
+ ),
3058
+ /* @__PURE__ */ jsx12(
3059
+ "button",
3060
+ {
3061
+ type: "button",
3062
+ disabled: creatingDoc,
3063
+ onClick: () => {
3064
+ setNamingDoc(false);
3065
+ setDocName("");
3066
+ },
3067
+ className: "flex h-7 items-center rounded-md border border-border px-2 text-[11px] text-muted-foreground hover:text-foreground disabled:opacity-50",
3068
+ children: "Cancel"
3069
+ }
3070
+ )
3071
+ ] }) : /* @__PURE__ */ jsxs10(Fragment4, { children: [
3072
+ actions.upload ? /* @__PURE__ */ jsx12(
3073
+ CaptureToolbarAction,
3074
+ {
3075
+ icon: isUploading ? SpinnerIcon : UploadIcon,
3076
+ spinning: isUploading,
3077
+ label: "Upload",
3078
+ onClick: () => !isUploading && fileInputRef.current?.click(),
3079
+ disabled: isPicking || isUploading
3080
+ }
3081
+ ) : null,
3082
+ actions.addFile ? /* @__PURE__ */ jsx12(
3083
+ CaptureToolbarAction,
3084
+ {
3085
+ icon: isPicking ? SpinnerIcon : FolderOpenIcon,
3086
+ spinning: isPicking,
3087
+ label: "Add file",
3088
+ onClick: () => void handlePickExisting(),
3089
+ disabled: isPicking || isUploading
3090
+ }
3091
+ ) : null,
3092
+ (actions.upload || actions.addFile) && actions.newDocument ? /* @__PURE__ */ jsx12("span", { className: "mx-1 h-4 w-px bg-border" }) : null,
3093
+ actions.newDocument ? /* @__PURE__ */ jsx12(
3094
+ CaptureToolbarAction,
3095
+ {
3096
+ icon: creatingDoc ? SpinnerIcon : PlusIcon,
3097
+ spinning: creatingDoc,
3098
+ label: "New document",
3099
+ onClick: () => setNamingDoc(true),
3100
+ disabled: creatingDoc
3101
+ }
3102
+ ) : null,
3103
+ extraActions
3104
+ ] }) }) : null,
3105
+ children
3106
+ ]
3107
+ }
3108
+ );
3109
+ }
3110
+ function CaptureToolbarAction({
3111
+ icon: Icon,
3112
+ label,
3113
+ onClick,
3114
+ disabled,
3115
+ spinning
3116
+ }) {
3117
+ return /* @__PURE__ */ jsxs10(
3118
+ Button,
3119
+ {
3120
+ type: "button",
3121
+ size: "sm",
3122
+ variant: "ghost",
3123
+ onClick,
3124
+ disabled,
3125
+ className: "h-7 gap-1 px-2 text-[11px] text-muted-foreground hover:text-foreground",
3126
+ children: [
3127
+ /* @__PURE__ */ jsx12(Icon, { className: cn9("size-3.5", spinning && "animate-spin") }),
3128
+ label
3129
+ ]
3130
+ }
3131
+ );
3132
+ }
3133
+
3134
+ // src/react/components/AssociationEntitySelect.tsx
3135
+ import { useState as useState12 } from "react";
3136
+ import {
3137
+ cn as cn10,
3138
+ Command,
3139
+ CommandEmpty,
3140
+ CommandGroup,
3141
+ CommandInput,
3142
+ CommandItem,
3143
+ CommandList,
3144
+ EditableLabel,
3145
+ Popover,
3146
+ PopoverContent,
3147
+ PopoverTrigger
3148
+ } from "@ai-matrx/design-system";
3149
+ import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
3150
+ function AssociationEntitySelect({
3151
+ token,
3152
+ adapter,
3153
+ className,
3154
+ labelClassName,
3155
+ align = "end",
3156
+ showIcon = true,
3157
+ renameActivation = "click",
3158
+ emptyLabel,
3159
+ iconClassName,
3160
+ createSlot
3161
+ }) {
3162
+ const store = useAssociationsStore();
3163
+ const { entityDoors } = useAssociationsUiPorts();
3164
+ const notifier = useNotifier();
3165
+ const info = store.registry.getEntityInfo(token);
3166
+ const InfoIcon = info.Icon ?? DefaultEntityIcon;
3167
+ const DoorControls = entityDoors?.DoorControls;
3168
+ const { items, activeId } = adapter;
3169
+ const active = items.find((i) => i.id === activeId) ?? null;
3170
+ const activeIndex = active ? items.indexOf(active) : -1;
3171
+ const [open, setOpen] = useState12(false);
3172
+ const [query, setQuery] = useState12("");
3173
+ const [creating, setCreating] = useState12(false);
3174
+ const [draftName, setDraftName] = useState12("");
3175
+ const [busy, setBusy] = useState12(false);
3176
+ const entityLabel = info.label.toLowerCase();
3177
+ const close = () => {
3178
+ setOpen(false);
3179
+ setQuery("");
3180
+ setCreating(false);
3181
+ setDraftName("");
3182
+ };
3183
+ const create = async (title) => {
3184
+ const name = title.trim();
3185
+ if (!name || busy || !adapter.createAndAttach) return;
3186
+ setBusy(true);
3187
+ const id = await adapter.createAndAttach(name);
3188
+ setBusy(false);
3189
+ if (id) close();
3190
+ else notifier.error(`Couldn't create the ${entityLabel}`);
3191
+ };
3192
+ return /* @__PURE__ */ jsxs11(
3193
+ "div",
3194
+ {
3195
+ className: cn10(
3196
+ "group/entity-ref flex min-w-0 items-center gap-0.5",
3197
+ className
3198
+ ),
3199
+ children: [
3200
+ showIcon ? /* @__PURE__ */ jsx13(
3201
+ InfoIcon,
3202
+ {
3203
+ className: cn10(
3204
+ "size-3.5 shrink-0",
3205
+ iconClassName ?? "text-muted-foreground"
3206
+ ),
3207
+ "aria-hidden": true
3208
+ }
3209
+ ) : null,
3210
+ active ? /* @__PURE__ */ jsx13(
3211
+ EditableLabel,
3212
+ {
3213
+ value: active.title,
3214
+ onCommit: async (next) => {
3215
+ const ok = await adapter.rename(active.id, next);
3216
+ if (!ok) {
3217
+ notifier.error(`Couldn't rename the ${entityLabel}`);
3218
+ throw new Error("rename failed");
3219
+ }
3220
+ },
3221
+ commitMode: "await",
3222
+ activation: renameActivation,
3223
+ ariaLabel: info.label,
3224
+ className: cn10("max-w-[14rem]", labelClassName),
3225
+ displayClassName: "text-xs font-medium text-foreground",
3226
+ inputClassName: "text-xs font-medium"
3227
+ }
3228
+ ) : null,
3229
+ active && DoorControls ? /* @__PURE__ */ jsx13(DoorControls, { token, id: active.id, name: active.title }) : null,
3230
+ !active ? /* @__PURE__ */ jsx13(
3231
+ "span",
3232
+ {
3233
+ className: cn10(
3234
+ "truncate px-1 text-xs font-medium text-muted-foreground",
3235
+ labelClassName
3236
+ ),
3237
+ children: adapter.loading ? "\u2026" : emptyLabel ?? info.labelPlural
3238
+ }
3239
+ ) : null,
3240
+ /* @__PURE__ */ jsxs11(
3241
+ Popover,
3242
+ {
3243
+ open,
3244
+ onOpenChange: (next) => next ? setOpen(true) : close(),
3245
+ children: [
3246
+ /* @__PURE__ */ jsx13(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs11(
3247
+ "button",
3248
+ {
3249
+ type: "button",
3250
+ className: "inline-flex h-6 shrink-0 items-center gap-0.5 rounded-md px-1 text-[10px] font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-foreground",
3251
+ title: `Switch or add ${info.labelPlural.toLowerCase()}`,
3252
+ "aria-label": `Switch or add ${info.labelPlural.toLowerCase()}`,
3253
+ children: [
3254
+ items.length > 1 ? /* @__PURE__ */ jsxs11("span", { className: "tabular-nums", children: [
3255
+ activeIndex >= 0 ? activeIndex + 1 : "\u2014",
3256
+ "/",
3257
+ items.length
3258
+ ] }) : null,
3259
+ /* @__PURE__ */ jsx13(ChevronDownIcon, { className: "size-3 opacity-60" })
3260
+ ]
3261
+ }
3262
+ ) }),
3263
+ /* @__PURE__ */ jsx13(PopoverContent, { className: "w-60 p-0", align, children: /* @__PURE__ */ jsxs11(Command, { children: [
3264
+ items.length > 5 ? /* @__PURE__ */ jsx13(
3265
+ CommandInput,
3266
+ {
3267
+ value: query,
3268
+ onValueChange: setQuery,
3269
+ placeholder: `Search ${info.labelPlural.toLowerCase()}\u2026`
3270
+ }
3271
+ ) : null,
3272
+ /* @__PURE__ */ jsxs11(CommandList, { children: [
3273
+ /* @__PURE__ */ jsx13(CommandEmpty, { children: "No match." }),
3274
+ items.length > 0 ? /* @__PURE__ */ jsx13(CommandGroup, { children: items.map((item) => /* @__PURE__ */ jsxs11(
3275
+ CommandItem,
3276
+ {
3277
+ value: `${item.title} ${item.id}`,
3278
+ onSelect: () => {
3279
+ if (item.id !== activeId)
3280
+ void adapter.setActive(item.id);
3281
+ close();
3282
+ },
3283
+ className: "group/entity-ref group gap-2",
3284
+ children: [
3285
+ /* @__PURE__ */ jsx13(
3286
+ CheckIcon,
3287
+ {
3288
+ className: cn10(
3289
+ "size-3.5 shrink-0",
3290
+ item.id === activeId ? "opacity-100" : "opacity-0"
3291
+ )
3292
+ }
3293
+ ),
3294
+ /* @__PURE__ */ jsx13("span", { className: "min-w-0 flex-1 truncate", children: item.title }),
3295
+ DoorControls ? /* @__PURE__ */ jsx13(
3296
+ "span",
3297
+ {
3298
+ className: "inline-flex shrink-0 items-center",
3299
+ onPointerDown: (e) => {
3300
+ e.preventDefault();
3301
+ e.stopPropagation();
3302
+ },
3303
+ children: /* @__PURE__ */ jsx13(
3304
+ DoorControls,
3305
+ {
3306
+ token,
3307
+ id: item.id,
3308
+ name: item.title
3309
+ }
3310
+ )
3311
+ }
3312
+ ) : null,
3313
+ adapter.detach && item.id !== activeId ? /* @__PURE__ */ jsx13(
3314
+ "button",
3315
+ {
3316
+ type: "button",
3317
+ onPointerDown: (e) => {
3318
+ e.preventDefault();
3319
+ e.stopPropagation();
3320
+ },
3321
+ onClick: (e) => {
3322
+ e.stopPropagation();
3323
+ void adapter.detach?.(item.id);
3324
+ },
3325
+ className: "grid size-5 shrink-0 place-items-center rounded text-muted-foreground opacity-0 transition-opacity hover:bg-accent hover:text-destructive group-hover:opacity-100",
3326
+ title: `Remove this ${entityLabel} from here (does not delete it)`,
3327
+ "aria-label": `Remove ${item.title}`,
3328
+ children: /* @__PURE__ */ jsx13(XIcon, { className: "size-3" })
3329
+ }
3330
+ ) : null
3331
+ ]
3332
+ },
3333
+ item.id
3334
+ )) }) : null
3335
+ ] }),
3336
+ createSlot !== void 0 ? /* @__PURE__ */ jsx13("div", { className: "border-t border-border p-1", children: typeof createSlot === "function" ? createSlot(close) : createSlot }) : null,
3337
+ createSlot === void 0 && adapter.createAndAttach ? /* @__PURE__ */ jsx13("div", { className: "border-t border-border p-1", children: creating ? /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-1 px-1 py-0.5", children: [
3338
+ /* @__PURE__ */ jsx13(
3339
+ "input",
3340
+ {
3341
+ autoFocus: true,
3342
+ type: "text",
3343
+ value: draftName,
3344
+ disabled: busy,
3345
+ placeholder: `New ${entityLabel} name\u2026`,
3346
+ onChange: (e) => setDraftName(e.target.value),
3347
+ onKeyDown: (e) => {
3348
+ if (e.key === "Enter") {
3349
+ e.preventDefault();
3350
+ void create(draftName);
3351
+ } else if (e.key === "Escape") {
3352
+ e.preventDefault();
3353
+ e.stopPropagation();
3354
+ setCreating(false);
3355
+ setDraftName("");
3356
+ }
3357
+ },
3358
+ className: "h-6 w-full min-w-0 rounded-sm bg-transparent px-1 text-base outline-none focus:bg-background focus:ring-1 focus:ring-ring md:text-xs",
3359
+ "aria-label": `New ${entityLabel} name`
3360
+ }
3361
+ ),
3362
+ /* @__PURE__ */ jsx13(
3363
+ "button",
3364
+ {
3365
+ type: "button",
3366
+ disabled: busy || !draftName.trim(),
3367
+ onClick: () => void create(draftName),
3368
+ className: "inline-flex h-6 shrink-0 items-center rounded-md px-1.5 text-[10px] font-medium text-primary transition-colors hover:bg-accent disabled:opacity-50",
3369
+ children: busy ? /* @__PURE__ */ jsx13(SpinnerIcon, { className: "size-3 animate-spin" }) : "Create"
3370
+ }
3371
+ )
3372
+ ] }) : /* @__PURE__ */ jsxs11(
3373
+ "button",
3374
+ {
3375
+ type: "button",
3376
+ disabled: busy,
3377
+ onClick: () => {
3378
+ if (query.trim()) void create(query);
3379
+ else setCreating(true);
3380
+ },
3381
+ className: "flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-xs text-muted-foreground transition-colors hover:bg-accent hover:text-foreground",
3382
+ children: [
3383
+ busy ? /* @__PURE__ */ jsx13(SpinnerIcon, { className: "size-3.5 animate-spin" }) : /* @__PURE__ */ jsx13(PlusIcon, { className: "size-3.5" }),
3384
+ query.trim() ? `Create "${query.trim()}"` : `New ${info.label}`
3385
+ ]
3386
+ }
3387
+ ) }) : null
3388
+ ] }) })
3389
+ ]
3390
+ }
3391
+ )
3392
+ ]
3393
+ }
3394
+ );
3395
+ }
3396
+
3397
+ // src/react/components/CategorySelect.tsx
3398
+ import { useState as useState13 } from "react";
3399
+ import {
3400
+ CreatablePicker,
3401
+ Select,
3402
+ SelectContent,
3403
+ SelectItem,
3404
+ SelectTrigger,
3405
+ SelectValue
3406
+ } from "@ai-matrx/design-system";
3407
+
3408
+ // src/core/categoryHierarchy.ts
3409
+ function buildCategoryHierarchy(categories) {
3410
+ const byId = new Map(categories.map((category) => [category.id, category]));
3411
+ const hasHierarchy = categories.some(
3412
+ (category) => category.parentId !== null && byId.has(category.parentId)
3413
+ );
3414
+ if (!hasHierarchy) {
3415
+ return {
3416
+ items: categories.map((category) => ({
3417
+ category,
3418
+ depth: 0,
3419
+ parent: null,
3420
+ displayName: category.name
3421
+ })),
3422
+ roots: categories.filter((category) => category.parentId === null),
3423
+ hasHierarchy: false
3424
+ };
3425
+ }
3426
+ const roots = categories.filter(
3427
+ (category) => category.parentId === null || !byId.has(category.parentId)
3428
+ );
3429
+ const childrenByParent = /* @__PURE__ */ new Map();
3430
+ for (const category of categories) {
3431
+ if (!category.parentId || !byId.has(category.parentId)) continue;
3432
+ const children = childrenByParent.get(category.parentId) ?? [];
3433
+ children.push(category);
3434
+ childrenByParent.set(category.parentId, children);
3435
+ }
3436
+ const items = [];
3437
+ const seen = /* @__PURE__ */ new Set();
3438
+ for (const root of roots) {
3439
+ items.push({
3440
+ category: root,
3441
+ depth: 0,
3442
+ parent: null,
3443
+ displayName: root.name
3444
+ });
3445
+ seen.add(root.id);
3446
+ for (const child of childrenByParent.get(root.id) ?? []) {
3447
+ items.push({
3448
+ category: child,
3449
+ depth: 1,
3450
+ parent: root,
3451
+ displayName: `${root.name} / ${child.name}`
3452
+ });
3453
+ seen.add(child.id);
3454
+ }
3455
+ }
3456
+ for (const category of categories) {
3457
+ if (seen.has(category.id)) continue;
3458
+ items.push({
3459
+ category,
3460
+ depth: 0,
3461
+ parent: null,
3462
+ displayName: category.name
3463
+ });
3464
+ }
3465
+ return {
3466
+ items,
3467
+ roots: categories.filter((category) => category.parentId === null),
3468
+ hasHierarchy: true
3469
+ };
3470
+ }
3471
+
3472
+ // src/react/components/CategorySelect.tsx
3473
+ import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
3474
+ var NONE = "__none__";
3475
+ var ROOT_PARENT = "__root__";
3476
+ function plural(noun) {
3477
+ return /[^aeiou]y$/i.test(noun) ? `${noun.slice(0, -1)}ies` : `${noun}s`;
3478
+ }
3479
+ function CategorySelect({
3480
+ dimension,
3481
+ value,
3482
+ onChange,
3483
+ placeholder,
3484
+ allowNone = true,
3485
+ disabled,
3486
+ allowCreate = true,
3487
+ orgId,
3488
+ noun = "category"
3489
+ }) {
3490
+ const store = useAssociationsStore();
3491
+ const notifier = useNotifier();
3492
+ const {
3493
+ categories,
3494
+ status,
3495
+ error,
3496
+ create: createCategory,
3497
+ reload
3498
+ } = useCategories({ dimension });
3499
+ const hierarchy = buildCategoryHierarchy(categories);
3500
+ const [createParentId, setCreateParentId] = useState13(ROOT_PARENT);
3501
+ const options = [
3502
+ ...allowNone ? [{ value: NONE, label: "None", keywords: "none clear empty" }] : [],
3503
+ ...hierarchy.hasHierarchy ? hierarchy.items.map(({ category, depth, parent }) => ({
3504
+ value: category.id,
3505
+ label: category.name,
3506
+ // The type-ahead matches the parent's name too, so someone who knows
3507
+ // the branch but not the leaf still finds it.
3508
+ keywords: parent ? parent.name : "",
3509
+ render: depth === 0 ? /* @__PURE__ */ jsx14("span", { className: "font-medium", children: category.name }) : /* @__PURE__ */ jsxs12("span", { className: "flex items-center gap-1.5 pl-3 text-muted-foreground", children: [
3510
+ /* @__PURE__ */ jsx14(CornerDownRightIcon, { className: "h-3.5 w-3.5 shrink-0" }),
3511
+ /* @__PURE__ */ jsx14("span", { className: "text-foreground", children: category.name })
3512
+ ] })
3513
+ })) : categories.map((category) => ({
3514
+ value: category.id,
3515
+ label: category.name
3516
+ }))
3517
+ ];
3518
+ const selected = value ? hierarchy.items.find((item) => item.category.id === value)?.displayName ?? categories.find((category) => category.id === value)?.name ?? null : null;
3519
+ const create = async (typed) => {
3520
+ const name = typed.trim();
3521
+ if (!name) return null;
3522
+ const existing = categories.find(
3523
+ (category) => category.name.toLowerCase() === name.toLowerCase()
3524
+ );
3525
+ if (existing) return existing.id;
3526
+ let resolvedOrgId = orgId ?? null;
3527
+ if (!resolvedOrgId && store.identity.ensureOrgId) {
3528
+ try {
3529
+ resolvedOrgId = await store.identity.ensureOrgId();
3530
+ } catch (e) {
3531
+ store.errorSink({
3532
+ code: "ensure_org_failed",
3533
+ message: "[CategorySelect] identity.ensureOrgId threw",
3534
+ context: { error: e instanceof Error ? e.message : String(e) }
3535
+ });
3536
+ }
3537
+ }
3538
+ if (!resolvedOrgId) {
3539
+ notifier.error(`Select an organization before creating a ${noun}.`);
3540
+ return null;
3541
+ }
3542
+ const parentId = hierarchy.hasHierarchy && createParentId !== ROOT_PARENT ? createParentId : null;
3543
+ const result = await createCategory({ name, orgId: resolvedOrgId, parentId });
3544
+ if (!result.ok || !result.id) {
3545
+ notifier.error(`Couldn't create that ${noun}`, {
3546
+ ...result.error != null ? { description: result.error } : {}
3547
+ });
3548
+ return null;
3549
+ }
3550
+ await reload();
3551
+ setCreateParentId(ROOT_PARENT);
3552
+ const under = parentId ? categories.find((category) => category.id === parentId)?.name : null;
3553
+ notifier.success(
3554
+ `\u201C${name}\u201D is now one of your ${plural(noun)}${under ? ` \u2014 it sits under ${under}, and every screen that offers ${plural(noun)} offers it.` : ` \u2014 every screen that offers ${plural(noun)} offers it from now on.`}`
3555
+ );
3556
+ return result.id;
3557
+ };
3558
+ return /* @__PURE__ */ jsxs12("div", { children: [
3559
+ /* @__PURE__ */ jsx14(
3560
+ CreatablePicker,
3561
+ {
3562
+ value: value ?? (allowNone ? NONE : null),
3563
+ onSelect: (next) => onChange(next === NONE ? null : next),
3564
+ options,
3565
+ placeholder,
3566
+ searchPlaceholder: allowCreate ? `Search or add a ${noun}\u2026` : `Search ${plural(noun)}\u2026`,
3567
+ noun,
3568
+ ariaLabel: placeholder,
3569
+ className: "text-sm",
3570
+ ...disabled !== void 0 ? { disabled } : {},
3571
+ loading: status === "loading",
3572
+ emptyLabel: `No ${noun} matches that.`,
3573
+ ...allowCreate ? { onCreate: create } : {},
3574
+ ...allowCreate && hierarchy.hasHierarchy ? {
3575
+ createExtra: /* @__PURE__ */ jsxs12(Select, { value: createParentId, onValueChange: setCreateParentId, children: [
3576
+ /* @__PURE__ */ jsx14(
3577
+ SelectTrigger,
3578
+ {
3579
+ className: "h-7 text-xs",
3580
+ "aria-label": `Put the new ${noun} under`,
3581
+ children: /* @__PURE__ */ jsx14(SelectValue, {})
3582
+ }
3583
+ ),
3584
+ /* @__PURE__ */ jsxs12(SelectContent, { children: [
3585
+ /* @__PURE__ */ jsx14(SelectItem, { value: ROOT_PARENT, className: "text-xs", children: "Top level" }),
3586
+ hierarchy.items.filter((item) => item.depth === 0).map((item) => /* @__PURE__ */ jsxs12(
3587
+ SelectItem,
3588
+ {
3589
+ value: item.category.id,
3590
+ className: "text-xs",
3591
+ children: [
3592
+ "Under ",
3593
+ item.category.name
3594
+ ]
3595
+ },
3596
+ item.category.id
3597
+ ))
3598
+ ] })
3599
+ ] })
3600
+ } : {}
3601
+ }
3602
+ ),
3603
+ selected === null && value !== null ? (
3604
+ // A stored id whose row this reader cannot see is not "nothing chosen":
3605
+ // saying so beats a blank box that looks like an unsaved field.
3606
+ /* @__PURE__ */ jsxs12("p", { className: "mt-1 text-xs text-muted-foreground", children: [
3607
+ "This ",
3608
+ noun,
3609
+ " is no longer available to you."
3610
+ ] })
3611
+ ) : null,
3612
+ error ? /* @__PURE__ */ jsx14("p", { className: "mt-1 text-xs text-destructive", children: error }) : null
3613
+ ] });
3614
+ }
3615
+
3616
+ // src/react/components/CategoryTagPicker.tsx
3617
+ import { useState as useState14 } from "react";
3618
+ import {
3619
+ Badge,
3620
+ Button as Button2,
3621
+ cn as cn11,
3622
+ Command as Command2,
3623
+ CommandEmpty as CommandEmpty2,
3624
+ CommandGroup as CommandGroup2,
3625
+ CommandInput as CommandInput2,
3626
+ CommandItem as CommandItem2,
3627
+ CommandList as CommandList2,
3628
+ Popover as Popover2,
3629
+ PopoverContent as PopoverContent2,
3630
+ PopoverTrigger as PopoverTrigger2,
3631
+ Select as Select2,
3632
+ SelectContent as SelectContent2,
3633
+ SelectItem as SelectItem2,
3634
+ SelectTrigger as SelectTrigger2,
3635
+ SelectValue as SelectValue2
3636
+ } from "@ai-matrx/design-system";
3637
+ import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
3638
+ var ROOT_PARENT2 = "__root__";
3639
+ function CategoryTagPicker({
3640
+ entityType,
3641
+ entityId,
3642
+ dimension,
3643
+ edgeRole,
3644
+ orgId,
3645
+ addLabel = "Add tag",
3646
+ icon: Icon = TagIcon,
3647
+ allowCreate = true,
3648
+ hierarchical,
3649
+ emptyText = "No categories yet."
3650
+ }) {
3651
+ const store = useAssociationsStore();
3652
+ const notifier = useNotifier();
3653
+ const [open, setOpen] = useState14(false);
3654
+ const [creating, setCreating] = useState14(false);
3655
+ const [writing, setWriting] = useState14(false);
3656
+ const [search, setSearch] = useState14("");
3657
+ const [createParentId, setCreateParentId] = useState14(ROOT_PARENT2);
3658
+ const {
3659
+ categories,
3660
+ create: createCategory,
3661
+ reload: reloadCategories
3662
+ } = useCategories({ dimension });
3663
+ const { edges, setTargets } = useAssociations({
3664
+ type: entityType,
3665
+ id: entityId
3666
+ });
3667
+ const hierarchy = buildCategoryHierarchy(categories);
3668
+ const showHierarchy = hierarchical ?? hierarchy.hasHierarchy;
3669
+ const hierarchyById = new Map(
3670
+ hierarchy.items.map((item) => [item.category.id, item])
3671
+ );
3672
+ const selectedIds = new Set(
3673
+ edges.filter(
3674
+ (e) => e.direction === "outgoing" && e.otherType === "category" && e.role === edgeRole
3675
+ ).map((e) => e.otherId)
3676
+ );
3677
+ const selected = categories.filter((c) => selectedIds.has(c.id));
3678
+ const toggle = async (categoryId) => {
3679
+ if (writing) return;
3680
+ setWriting(true);
3681
+ try {
3682
+ const next = selectedIds.has(categoryId) ? [...selectedIds].filter((id) => id !== categoryId) : [...selectedIds, categoryId];
3683
+ const res = await setTargets({
3684
+ targetType: "category",
3685
+ targetIds: next,
3686
+ ...orgId != null ? { orgId } : {},
3687
+ role: edgeRole
3688
+ });
3689
+ if (!res.ok) {
3690
+ store.errorSink({
3691
+ code: "category_tag_set_targets_failed",
3692
+ message: "[CategoryTagPicker] setTargets failed",
3693
+ context: { entityType, entityId, edgeRole, error: res.error }
3694
+ });
3695
+ notifier.error(res.error ?? "Could not update categories");
3696
+ }
3697
+ } finally {
3698
+ setWriting(false);
3699
+ }
3700
+ };
3701
+ const createAndAttach = async () => {
3702
+ const name = search.trim();
3703
+ if (!name) return;
3704
+ setCreating(true);
3705
+ try {
3706
+ let resolvedOrgId = orgId ?? null;
3707
+ if (!resolvedOrgId && store.identity.ensureOrgId) {
3708
+ try {
3709
+ resolvedOrgId = await store.identity.ensureOrgId();
3710
+ } catch (e) {
3711
+ store.errorSink({
3712
+ code: "ensure_org_failed",
3713
+ message: "[CategoryTagPicker] identity.ensureOrgId threw",
3714
+ context: { error: e instanceof Error ? e.message : String(e) }
3715
+ });
3716
+ }
3717
+ }
3718
+ if (!resolvedOrgId) {
3719
+ store.errorSink({
3720
+ code: "ensure_org_missing",
3721
+ message: "[CategoryTagPicker] no org for the created category \u2014 pass `orgId` or bind `identity.ensureOrgId` on the store config",
3722
+ context: { entityType, entityId, dimension }
3723
+ });
3724
+ notifier.error(
3725
+ "Could not create the category \u2014 no organization is selected"
3726
+ );
3727
+ return;
3728
+ }
3729
+ const parentId = showHierarchy && createParentId !== ROOT_PARENT2 ? createParentId : null;
3730
+ const createRes = await createCategory({
3731
+ name,
3732
+ orgId: resolvedOrgId,
3733
+ parentId
3734
+ });
3735
+ if (!createRes.ok || !createRes.id) {
3736
+ store.errorSink({
3737
+ code: "category_tag_create_failed",
3738
+ message: "[CategoryTagPicker] create failed",
3739
+ context: { dimension, error: createRes.error }
3740
+ });
3741
+ notifier.error(createRes.error ?? "Could not create the category");
3742
+ return;
3743
+ }
3744
+ await reloadCategories();
3745
+ await toggle(createRes.id);
3746
+ setSearch("");
3747
+ setCreateParentId(ROOT_PARENT2);
3748
+ } finally {
3749
+ setCreating(false);
3750
+ }
3751
+ };
3752
+ const filtered = categories.filter(
3753
+ (c) => c.name.toLowerCase().includes(search.trim().toLowerCase())
3754
+ );
3755
+ const filteredHierarchy = hierarchy.items.filter(
3756
+ (item) => item.displayName.toLowerCase().includes(search.trim().toLowerCase())
3757
+ );
3758
+ const exactMatch = categories.some(
3759
+ (c) => c.name.toLowerCase() === search.trim().toLowerCase()
3760
+ );
3761
+ return /* @__PURE__ */ jsxs13("div", { className: "flex flex-wrap items-center gap-1.5", children: [
3762
+ selected.map((c) => /* @__PURE__ */ jsxs13(
3763
+ Badge,
3764
+ {
3765
+ variant: "secondary",
3766
+ className: "gap-1 pr-1 text-xs font-medium",
3767
+ children: [
3768
+ showHierarchy ? hierarchyById.get(c.id)?.displayName ?? c.name : c.name,
3769
+ /* @__PURE__ */ jsx15(
3770
+ "button",
3771
+ {
3772
+ type: "button",
3773
+ onClick: () => void toggle(c.id),
3774
+ className: "ml-0.5 rounded-full p-0.5 hover:bg-muted-foreground/20",
3775
+ "aria-label": `Remove ${c.name}`,
3776
+ children: /* @__PURE__ */ jsx15(XIcon, { className: "h-2.5 w-2.5" })
3777
+ }
3778
+ )
3779
+ ]
3780
+ },
3781
+ c.id
3782
+ )),
3783
+ /* @__PURE__ */ jsxs13(Popover2, { open, onOpenChange: setOpen, children: [
3784
+ /* @__PURE__ */ jsx15(PopoverTrigger2, { asChild: true, children: /* @__PURE__ */ jsxs13(
3785
+ Button2,
3786
+ {
3787
+ variant: "outline",
3788
+ size: "sm",
3789
+ className: "h-7 gap-1 px-2 text-xs",
3790
+ children: [
3791
+ /* @__PURE__ */ jsx15(Icon, { className: "h-3.5 w-3.5" }),
3792
+ selected.length === 0 ? addLabel : "Edit",
3793
+ /* @__PURE__ */ jsx15(ChevronsUpDownIcon, { className: "h-3 w-3 opacity-50" })
3794
+ ]
3795
+ }
3796
+ ) }),
3797
+ /* @__PURE__ */ jsx15(PopoverContent2, { className: "w-56 p-0", align: "start", children: /* @__PURE__ */ jsxs13(Command2, { shouldFilter: false, children: [
3798
+ /* @__PURE__ */ jsx15(
3799
+ CommandInput2,
3800
+ {
3801
+ placeholder: allowCreate ? "Search or create..." : "Search...",
3802
+ value: search,
3803
+ onValueChange: setSearch
3804
+ }
3805
+ ),
3806
+ /* @__PURE__ */ jsxs13(CommandList2, { children: [
3807
+ /* @__PURE__ */ jsx15(CommandEmpty2, { className: "px-2 py-3 text-xs text-muted-foreground", children: search.trim() ? "No match." : emptyText }),
3808
+ /* @__PURE__ */ jsx15(CommandGroup2, { children: showHierarchy ? filteredHierarchy.map(({ category, depth, parent }) => /* @__PURE__ */ jsxs13(
3809
+ CommandItem2,
3810
+ {
3811
+ value: category.id,
3812
+ keywords: parent ? [parent.name, category.name] : [],
3813
+ onSelect: () => void toggle(category.id),
3814
+ className: cn11("text-xs", depth === 1 && "pl-6"),
3815
+ children: [
3816
+ /* @__PURE__ */ jsx15(
3817
+ CheckIcon,
3818
+ {
3819
+ className: cn11(
3820
+ "mr-2 h-3.5 w-3.5",
3821
+ selectedIds.has(category.id) ? "opacity-100" : "opacity-0"
3822
+ )
3823
+ }
3824
+ ),
3825
+ depth === 1 ? /* @__PURE__ */ jsx15(CornerDownRightIcon, { className: "mr-1.5 h-3.5 w-3.5 shrink-0 text-muted-foreground" }) : null,
3826
+ /* @__PURE__ */ jsx15("span", { className: cn11(depth === 0 && "font-medium"), children: category.name })
3827
+ ]
3828
+ },
3829
+ category.id
3830
+ )) : filtered.map((c) => /* @__PURE__ */ jsxs13(
3831
+ CommandItem2,
3832
+ {
3833
+ value: c.id,
3834
+ onSelect: () => void toggle(c.id),
3835
+ className: "text-xs",
3836
+ children: [
3837
+ /* @__PURE__ */ jsx15(
3838
+ CheckIcon,
3839
+ {
3840
+ className: cn11(
3841
+ "mr-2 h-3.5 w-3.5",
3842
+ selectedIds.has(c.id) ? "opacity-100" : "opacity-0"
3843
+ )
3844
+ }
3845
+ ),
3846
+ c.name
3847
+ ]
3848
+ },
3849
+ c.id
3850
+ )) }),
3851
+ allowCreate && search.trim() && !exactMatch ? /* @__PURE__ */ jsxs13(CommandGroup2, { children: [
3852
+ showHierarchy ? /* @__PURE__ */ jsxs13("div", { className: "space-y-1.5 px-2 pb-2", children: [
3853
+ /* @__PURE__ */ jsx15("p", { className: "text-[11px] font-medium text-muted-foreground", children: "Place in" }),
3854
+ /* @__PURE__ */ jsxs13(
3855
+ Select2,
3856
+ {
3857
+ value: createParentId,
3858
+ onValueChange: setCreateParentId,
3859
+ children: [
3860
+ /* @__PURE__ */ jsx15(SelectTrigger2, { size: "sm", className: "bg-background", children: /* @__PURE__ */ jsx15(SelectValue2, {}) }),
3861
+ /* @__PURE__ */ jsxs13(SelectContent2, { children: [
3862
+ /* @__PURE__ */ jsx15(SelectItem2, { value: ROOT_PARENT2, children: "Top level" }),
3863
+ hierarchy.roots.map((root) => /* @__PURE__ */ jsx15(SelectItem2, { value: root.id, children: root.name }, root.id))
3864
+ ] })
3865
+ ]
3866
+ }
3867
+ )
3868
+ ] }) : null,
3869
+ /* @__PURE__ */ jsxs13(
3870
+ CommandItem2,
3871
+ {
3872
+ value: `__create__${search}`,
3873
+ onSelect: () => void createAndAttach(),
3874
+ disabled: creating,
3875
+ className: "text-xs text-primary",
3876
+ children: [
3877
+ creating ? /* @__PURE__ */ jsx15(SpinnerIcon, { className: "mr-2 h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsx15(Icon, { className: "mr-2 h-3.5 w-3.5" }),
3878
+ "Create \u201C",
3879
+ search.trim(),
3880
+ "\u201D"
3881
+ ]
3882
+ }
3883
+ )
3884
+ ] }) : null
3885
+ ] })
3886
+ ] }) })
3887
+ ] })
3888
+ ] });
3889
+ }
3890
+ export {
3891
+ AssociationCandidateBody,
3892
+ AssociationCaptureToolbar,
3893
+ AssociationCard,
3894
+ AssociationCardGrid,
3895
+ AssociationEntitySelect,
3896
+ AssociationList,
3897
+ AssociationPicker,
3898
+ AssociationWindow,
3899
+ AssociationsProvider,
3900
+ AttachedItemsSheet,
3901
+ CONTENT_ROLES,
3902
+ CaptureToolbarAction,
3903
+ CategorySelect,
3904
+ CategoryTagPicker,
3905
+ DefaultEntityIcon,
3906
+ DoorRef,
3907
+ PrimaryEntityProvider,
3908
+ UniversalAssociationPicker,
3909
+ UnresolvedRef,
3910
+ attachedKey,
3911
+ getContentRoleMeta,
3912
+ useAssociationCandidates,
3913
+ useAssociationEntitySelectAdapter,
3914
+ useAssociations,
3915
+ useAssociationsStore,
3916
+ useAssociationsUiPorts,
3917
+ useCategories,
3918
+ useContainerLinks,
3919
+ useContainerLinksAdapter,
3920
+ useEntityRelationships,
3921
+ useEntityTitles,
3922
+ useNotifier,
3923
+ usePrimaryEntity,
3924
+ useUniversalEntitySearch
3925
+ };
3926
+ //# sourceMappingURL=index.js.map