@ai-matrx/associations 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3142 @@
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_outcome_event",
507
+ "platform_outsider_consumer",
508
+ "platform_saved_view",
509
+ "podcast_race",
510
+ "processed_document",
511
+ "processed_document_page",
512
+ "product_capture_file",
513
+ "product_capture_item",
514
+ "product_capture_payload",
515
+ "product_capture_product",
516
+ "product_capture_question",
517
+ "project",
518
+ "provision",
519
+ "purpose",
520
+ "quiz_session",
521
+ "rag_ingest_run",
522
+ "redaction_mapping",
523
+ "research_analysis",
524
+ "research_content",
525
+ "research_context_bundle",
526
+ "research_document",
527
+ "research_keyword",
528
+ "research_media",
529
+ "research_source",
530
+ "research_synthesis",
531
+ "research_tag",
532
+ "research_template",
533
+ "research_topic",
534
+ "rulebook",
535
+ "sandbox_instance",
536
+ "sch_agent_task",
537
+ "sch_run",
538
+ "sch_task",
539
+ "sch_trigger",
540
+ "scope",
541
+ "scope_association_suggestion",
542
+ "scope_item_value_suggestion",
543
+ "scope_suggestion",
544
+ "scope_type",
545
+ "seo_ai_visibility_citation",
546
+ "seo_ai_visibility_claim",
547
+ "seo_ai_visibility_panel",
548
+ "seo_ai_visibility_response",
549
+ "seo_ai_visibility_signal",
550
+ "seo_backlink",
551
+ "seo_backlink_change_event",
552
+ "seo_backlink_dimension_snapshot",
553
+ "seo_backlink_observation",
554
+ "seo_backlink_snapshot",
555
+ "seo_change_assessment",
556
+ "seo_change_event",
557
+ "seo_change_item",
558
+ "seo_change_metric",
559
+ "seo_change_set",
560
+ "seo_change_theory",
561
+ "seo_collection_run",
562
+ "seo_competitor",
563
+ "seo_competitor_observation",
564
+ "seo_competitor_opportunity",
565
+ "seo_coverage_mention",
566
+ "seo_coverage_tracker",
567
+ "seo_dimension_value_matcher",
568
+ "seo_engine_schedule",
569
+ "seo_geo_place",
570
+ "seo_gsc_dig_rule",
571
+ "seo_keyword",
572
+ "seo_keyword_class_rule",
573
+ "seo_keyword_edge",
574
+ "seo_keyword_facet",
575
+ "seo_keyword_market",
576
+ "seo_keyword_market_observation",
577
+ "seo_keyword_place",
578
+ "seo_keyword_saved_view",
579
+ "seo_keyword_topic",
580
+ "seo_landscape_brief",
581
+ "seo_link_gap_domain",
582
+ "seo_link_gap_match",
583
+ "seo_page_measurement_health",
584
+ "seo_page_performance",
585
+ "seo_provider_call",
586
+ "seo_provider_task",
587
+ "seo_rank_observation",
588
+ "seo_rank_target",
589
+ "seo_raw_payload",
590
+ "seo_referring_domain_profile",
591
+ "seo_reputation_case",
592
+ "seo_search_performance_daily",
593
+ "seo_serp_mention",
594
+ "seo_serp_opportunity",
595
+ "seo_serp_result",
596
+ "seo_serp_snapshot",
597
+ "seo_site_geo_area",
598
+ "seo_site_keyword_offering",
599
+ "seo_site_keyword_value",
600
+ "seo_site_offering_value",
601
+ "seo_site_topic_value",
602
+ "seo_site_value_combo",
603
+ "seo_site_value_worth",
604
+ "seo_site_vocabulary",
605
+ "seo_source_request",
606
+ "seo_starter_pack",
607
+ "seo_starter_pack_item",
608
+ "seo_story_angle",
609
+ "seo_topic",
610
+ "seo_web_analytics_daily",
611
+ "shared_canvas_item",
612
+ "skill",
613
+ "skill_render_definition",
614
+ "sms_consent",
615
+ "sms_conversation",
616
+ "sms_message",
617
+ "sms_message_media",
618
+ "sms_notification",
619
+ "sms_notification_preference",
620
+ "sms_phone_number",
621
+ "structured_list",
622
+ "studio_documents",
623
+ "studio_recording_chunks",
624
+ "studio_recording_segments",
625
+ "studio_run",
626
+ "studio_session",
627
+ "studio_session_settings",
628
+ "study_attempt",
629
+ "study_goal",
630
+ "study_media",
631
+ "study_plan",
632
+ "study_plan_block",
633
+ "study_plan_day",
634
+ "study_reminder_context",
635
+ "study_reminder_delivery",
636
+ "study_session",
637
+ "surface",
638
+ "system_context_item",
639
+ "system_error",
640
+ "system_personal_org_failure",
641
+ "system_write_failure",
642
+ "task",
643
+ "thread",
644
+ "tool",
645
+ "tool_bundle",
646
+ "tool_call",
647
+ "tool_definition_version",
648
+ "tool_test_sample",
649
+ "tool_ui",
650
+ "tool_ui_incident",
651
+ "tool_ui_version",
652
+ "transcript",
653
+ "udt_dataset_fields",
654
+ "udt_dataset_rows",
655
+ "udt_document",
656
+ "udt_structured_list_items",
657
+ "ui_surface_agent_pref",
658
+ "ui_surface_config",
659
+ "user_achievement",
660
+ "user_analysis_preference",
661
+ "user_bookmark",
662
+ "user_email_preference",
663
+ "user_feedback",
664
+ "user_form_profile",
665
+ "user_markdown_sample",
666
+ "user_memory",
667
+ "user_preference",
668
+ "user_profile",
669
+ "user_stat",
670
+ "user_surface_state",
671
+ "voice",
672
+ "war_room",
673
+ "wbx_capture",
674
+ "wbx_guidance",
675
+ "wbx_highlight",
676
+ "wbx_pattern",
677
+ "wbx_screenshot",
678
+ "wbx_seo_audit",
679
+ "wc_claim",
680
+ "wc_impairment_definition",
681
+ "wc_injury",
682
+ "wc_report",
683
+ "web_analysis_item",
684
+ "web_brand",
685
+ "web_brand_asset",
686
+ "web_brand_offering",
687
+ "web_business_fact",
688
+ "web_business_location",
689
+ "web_crawl_event",
690
+ "web_crawl_preset",
691
+ "web_crawl_schedule",
692
+ "web_crawl_session",
693
+ "web_crawl_url",
694
+ "web_discovered_item",
695
+ "web_finding",
696
+ "web_gsc_page_stat",
697
+ "web_link_edge",
698
+ "web_listing_publisher",
699
+ "web_location_listing",
700
+ "web_offering_template",
701
+ "web_page",
702
+ "web_page_content",
703
+ "web_page_evidence",
704
+ "web_page_sitemap",
705
+ "web_property",
706
+ "web_provider",
707
+ "web_result",
708
+ "web_screenshot",
709
+ "web_site",
710
+ "web_site_endpoint_rule",
711
+ "web_site_item_config",
712
+ "web_site_offering",
713
+ "web_sitemap",
714
+ "web_snapshot",
715
+ "wf_node_data_slot",
716
+ "work_item",
717
+ "workbook",
718
+ "workflow",
719
+ "workflow_card",
720
+ "workflow_checkpoint",
721
+ "workflow_definition_version",
722
+ "workflow_idempotency",
723
+ "workflow_job",
724
+ "workflow_node_events",
725
+ "workflow_node_outcome",
726
+ "workflow_plan",
727
+ "workflow_plan_event",
728
+ "workflow_plan_sample",
729
+ "workflow_recovery_audit",
730
+ "workflow_run",
731
+ "workflow_run_log",
732
+ "workflow_runtime_surface",
733
+ "workflow_template",
734
+ "workflow_trigger",
735
+ "workflow_trigger_fire",
736
+ "working_document",
737
+ "youtube_search",
738
+ "youtube_video"
739
+ ];
740
+ var ENTITY_TYPE_TOKEN_SET = new Set(ENTITY_TYPE_TOKENS);
741
+ function isEntityTypeToken(value) {
742
+ return typeof value === "string" && ENTITY_TYPE_TOKEN_SET.has(value);
743
+ }
744
+
745
+ // src/core/titles.ts
746
+ function entityTitleCacheKey(token, id) {
747
+ return `${token}:${id}`;
748
+ }
749
+
750
+ // src/core/store.ts
751
+ function associationsKey(type, id) {
752
+ return `${type}:${id}`;
753
+ }
754
+ var IDLE_ASSOCIATIONS = Object.freeze({
755
+ status: "idle",
756
+ edges: Object.freeze([]),
757
+ fetchedAt: null,
758
+ error: null
759
+ });
760
+ var IDLE_CATEGORIES = Object.freeze({
761
+ status: "idle",
762
+ categories: Object.freeze([]),
763
+ fetchedAt: null,
764
+ error: null
765
+ });
766
+
767
+ // src/react/hooks/useAssociations.ts
768
+ var noopSubscribe = () => () => {
769
+ };
770
+ function useAssociations(args) {
771
+ const { type, id, autoLoad = true } = args;
772
+ const store = useAssociationsStore();
773
+ const key = type && id ? associationsKey(type, id) : null;
774
+ const entry = useSyncExternalStore(
775
+ key ? (cb) => store.subscribe(key, cb) : noopSubscribe,
776
+ () => store.getEdges(type, id ?? ""),
777
+ () => store.getEdges(type, id ?? "")
778
+ );
779
+ const loadedKey = useRef2(null);
780
+ useEffect(() => {
781
+ if (!autoLoad || !type || !id) return;
782
+ const k = associationsKey(type, id);
783
+ if (loadedKey.current === k) return;
784
+ loadedKey.current = k;
785
+ void store.load(type, id);
786
+ }, [autoLoad, store, type, id]);
787
+ return {
788
+ edges: entry.edges,
789
+ status: entry.status,
790
+ error: entry.error,
791
+ fetchedAt: entry.fetchedAt,
792
+ add: async ({ targetType, targetId, orgId, label, role }) => {
793
+ if (!id) return { ok: false, error: "Missing entity id" };
794
+ return store.add({
795
+ sourceType: type,
796
+ sourceId: id,
797
+ targetType,
798
+ targetId,
799
+ ...orgId !== void 0 ? { orgId } : {},
800
+ ...label !== void 0 ? { label } : {},
801
+ ...role !== void 0 ? { role } : {}
802
+ });
803
+ },
804
+ remove: async ({ targetType, targetId, role }) => {
805
+ if (!id) return { ok: false, error: "Missing entity id" };
806
+ return store.remove({
807
+ sourceType: type,
808
+ sourceId: id,
809
+ targetType,
810
+ targetId,
811
+ ...role !== void 0 ? { role } : {}
812
+ });
813
+ },
814
+ setTargets: async ({ targetType, targetIds, orgId, role }) => {
815
+ if (!id) return { ok: false, error: "Missing entity id" };
816
+ return store.setTargets({
817
+ sourceType: type,
818
+ sourceId: id,
819
+ targetType,
820
+ targetIds,
821
+ ...orgId !== void 0 ? { orgId } : {},
822
+ ...role !== void 0 ? { role } : {}
823
+ });
824
+ },
825
+ reload: async () => {
826
+ if (!type || !id) return;
827
+ await store.load(type, id, { force: true });
828
+ }
829
+ };
830
+ }
831
+ var useEntityRelationships = useAssociations;
832
+
833
+ // src/react/hooks/useContainerLinks.ts
834
+ import {
835
+ useCallback,
836
+ useEffect as useEffect2,
837
+ useLayoutEffect,
838
+ useRef as useRef3,
839
+ useState
840
+ } from "react";
841
+ function useContainerLinks(args) {
842
+ const { containerType, containerId, orgId } = args;
843
+ const store = useAssociationsStore();
844
+ const {
845
+ edges,
846
+ status: genericStatus,
847
+ error: genericError,
848
+ reload: reloadGeneric
849
+ } = useAssociations({
850
+ type: containerType,
851
+ id: containerId
852
+ });
853
+ const conversationKey = containerType === "conversation" && containerId ? `conversation:${containerId}` : null;
854
+ const activeConversationKey = useRef3(conversationKey);
855
+ const conversationGeneration = useRef3(0);
856
+ const [conversationResult, setConversationResult] = useState(null);
857
+ useLayoutEffect(() => {
858
+ activeConversationKey.current = conversationKey;
859
+ conversationGeneration.current += 1;
860
+ }, [conversationKey]);
861
+ const loadConversationFiles = useCallback(async () => {
862
+ const key = conversationKey;
863
+ if (!key || !containerId) return;
864
+ if (activeConversationKey.current !== key) return;
865
+ const generation = ++conversationGeneration.current;
866
+ setConversationResult((current) => ({
867
+ key,
868
+ files: current?.key === key ? current.files : [],
869
+ // Keep the visible failure context while retrying so the same control
870
+ // can disable itself and show progress instead of disappearing.
871
+ error: current?.key === key ? current.error : null,
872
+ loading: true
873
+ }));
874
+ const result = await store.services.associations.listConversationFiles(containerId);
875
+ if (generation !== conversationGeneration.current || activeConversationKey.current !== key) {
876
+ return;
877
+ }
878
+ if (isAssociationsRpcErr(result)) {
879
+ setConversationResult((current) => ({
880
+ key,
881
+ files: current?.key === key ? current.files : [],
882
+ error: result.error.message,
883
+ loading: false
884
+ }));
885
+ return;
886
+ }
887
+ setConversationResult({
888
+ key,
889
+ files: result.data.files.map((file) => ({
890
+ edgeId: `conversation-file:${file.fileId}`,
891
+ resourceId: file.fileId,
892
+ token: "file",
893
+ role: null,
894
+ label: file.label,
895
+ metadata: file.metadata
896
+ })),
897
+ error: null,
898
+ loading: false
899
+ });
900
+ }, [containerId, conversationKey, store]);
901
+ useEffect2(() => {
902
+ const timeout = setTimeout(() => {
903
+ void loadConversationFiles();
904
+ }, 0);
905
+ return () => clearTimeout(timeout);
906
+ }, [loadConversationFiles]);
907
+ const conversationFiles = conversationResult?.key === conversationKey ? conversationResult.files : [];
908
+ const conversationFileError = conversationResult?.key === conversationKey ? conversationResult.error : null;
909
+ const conversationFileStatus = containerType !== "conversation" ? "idle" : conversationResult?.key !== conversationKey ? "loading" : conversationResult.loading ? "loading" : conversationFileError ? "error" : "ready";
910
+ const incomingGeneric = edges.filter(
911
+ (edge) => edge.direction === "incoming" && !(containerType === "conversation" && edge.otherType === "file")
912
+ ).map((edge) => ({
913
+ edgeId: edge.id,
914
+ resourceId: edge.otherId,
915
+ token: edge.otherType,
916
+ role: edge.role ?? null,
917
+ label: edge.label ?? null,
918
+ metadata: edge.metadata ?? {}
919
+ }));
920
+ const incoming = containerType === "conversation" ? [...incomingGeneric, ...conversationFiles] : incomingGeneric;
921
+ const linksFor = (token) => incoming.filter((link) => link.token === token);
922
+ const countFor = (token) => incoming.reduce((n, link) => link.token === token ? n + 1 : n, 0);
923
+ const attachedIdsFor = (token) => {
924
+ const set = /* @__PURE__ */ new Set();
925
+ for (const link of incoming) {
926
+ if (link.token === token) set.add(link.resourceId);
927
+ }
928
+ return set;
929
+ };
930
+ const attach = async (token, resourceId, label, metadata, options) => {
931
+ if (!containerId) return { ok: false, error: "Missing container id" };
932
+ const result = await store.add({
933
+ sourceType: token,
934
+ sourceId: resourceId,
935
+ targetType: containerType,
936
+ targetId: containerId,
937
+ ...orgId != null ? { orgId } : {},
938
+ ...label !== void 0 ? { label } : {},
939
+ ...metadata !== void 0 ? { metadata } : {},
940
+ ...options?.role !== void 0 ? { role: options.role } : {},
941
+ ...options?.replaceMetadata !== void 0 ? { replaceMetadata: options.replaceMetadata } : {}
942
+ });
943
+ if (result.ok && containerType === "conversation" && token === "file") {
944
+ await loadConversationFiles();
945
+ }
946
+ return result;
947
+ };
948
+ const detach = async (token, resourceId, role) => {
949
+ if (!containerId) return { ok: false, error: "Missing container id" };
950
+ const result = await store.remove({
951
+ sourceType: token,
952
+ sourceId: resourceId,
953
+ targetType: containerType,
954
+ targetId: containerId,
955
+ ...role !== void 0 ? { role } : {}
956
+ });
957
+ if (result.ok && containerType === "conversation" && token === "file") {
958
+ await loadConversationFiles();
959
+ }
960
+ return result;
961
+ };
962
+ const reload = async () => {
963
+ await Promise.all([
964
+ reloadGeneric(),
965
+ containerType === "conversation" ? loadConversationFiles() : Promise.resolve()
966
+ ]);
967
+ };
968
+ const status = !containerId ? genericStatus : conversationFileStatus === "error" ? "error" : containerType === "conversation" && (conversationFileStatus === "idle" || conversationFileStatus === "loading") ? "loading" : genericStatus;
969
+ const error = conversationFileError ?? genericError;
970
+ return {
971
+ status,
972
+ error,
973
+ reload,
974
+ totalCount: incoming.length,
975
+ countFor,
976
+ attachedIdsFor,
977
+ linksFor,
978
+ attach,
979
+ detach
980
+ };
981
+ }
982
+
983
+ // src/react/hooks/useEntityTitles.ts
984
+ import { useEffect as useEffect3, useState as useState2 } from "react";
985
+ function useEntityTitles(refs) {
986
+ const store = useAssociationsStore();
987
+ const [resolved, setResolved] = useState2({});
988
+ const [attempted, setAttempted] = useState2({});
989
+ const [loading, setLoading] = useState2(false);
990
+ const needed = refs.filter(
991
+ (r) => store.registry.tryGetEntityInfo(r.token)?.titleColumn != null && store.titles.get(r.token, r.id) == null && !attempted[entityTitleCacheKey(r.token, r.id)]
992
+ );
993
+ const neededKey = needed.map((r) => entityTitleCacheKey(r.token, r.id)).sort().join("|");
994
+ useEffect3(() => {
995
+ if (!neededKey) return;
996
+ let cancelled = false;
997
+ setLoading(true);
998
+ const byToken = /* @__PURE__ */ new Map();
999
+ for (const key of neededKey.split("|")) {
1000
+ const sep = key.indexOf(":");
1001
+ const token = key.slice(0, sep);
1002
+ const id = key.slice(sep + 1);
1003
+ const list = byToken.get(token) ?? [];
1004
+ list.push(id);
1005
+ byToken.set(token, list);
1006
+ }
1007
+ void Promise.all(
1008
+ [...byToken.entries()].map(async ([token, ids]) => {
1009
+ const titles = await store.titles.fetch(token, ids);
1010
+ return [token, titles];
1011
+ })
1012
+ ).then((results) => {
1013
+ if (cancelled) return;
1014
+ setResolved((prev) => {
1015
+ const next = { ...prev };
1016
+ for (const [token, titles] of results) {
1017
+ for (const [id, title] of titles) {
1018
+ next[entityTitleCacheKey(token, id)] = title;
1019
+ }
1020
+ }
1021
+ return next;
1022
+ });
1023
+ }).catch((err) => {
1024
+ if (cancelled) return;
1025
+ store.errorSink({
1026
+ code: "title_resolution_failed",
1027
+ message: "Failed to resolve entity titles",
1028
+ context: { detail: err }
1029
+ });
1030
+ }).finally(() => {
1031
+ if (cancelled) return;
1032
+ setAttempted((prev) => {
1033
+ const next = { ...prev };
1034
+ for (const key of neededKey.split("|")) next[key] = true;
1035
+ return next;
1036
+ });
1037
+ setLoading(false);
1038
+ });
1039
+ return () => {
1040
+ cancelled = true;
1041
+ };
1042
+ }, [neededKey, store]);
1043
+ const titleFor = (ref) => {
1044
+ if (ref.label && ref.label.trim()) return ref.label;
1045
+ const key = entityTitleCacheKey(ref.token, ref.id);
1046
+ return resolved[key] ?? store.titles.get(ref.token, ref.id) ?? store.titles.fallback(ref.token);
1047
+ };
1048
+ const isUnresolved = (ref) => {
1049
+ if (!store.registry.tryGetEntityInfo(ref.token)?.titleColumn) return false;
1050
+ const key = entityTitleCacheKey(ref.token, ref.id);
1051
+ if (resolved[key] || store.titles.get(ref.token, ref.id)) return false;
1052
+ return attempted[key] === true;
1053
+ };
1054
+ return { titleFor, isUnresolved, loading };
1055
+ }
1056
+
1057
+ // src/react/hooks/useCategories.ts
1058
+ import { useEffect as useEffect4, useRef as useRef4, useSyncExternalStore as useSyncExternalStore2 } from "react";
1059
+ var noopSubscribe2 = () => () => {
1060
+ };
1061
+ function useCategories(args) {
1062
+ const { dimension, autoLoad = true } = args;
1063
+ const store = useAssociationsStore();
1064
+ const entry = useSyncExternalStore2(
1065
+ dimension ? (cb) => store.subscribeCategories(dimension, cb) : noopSubscribe2,
1066
+ () => store.getCategories(dimension ?? ""),
1067
+ () => store.getCategories(dimension ?? "")
1068
+ );
1069
+ const loadedKey = useRef4(null);
1070
+ useEffect4(() => {
1071
+ if (!autoLoad || !dimension) return;
1072
+ if (loadedKey.current === dimension) return;
1073
+ loadedKey.current = dimension;
1074
+ void store.loadCategories(dimension);
1075
+ }, [autoLoad, store, dimension]);
1076
+ return {
1077
+ categories: entry.categories,
1078
+ status: entry.status,
1079
+ error: entry.error,
1080
+ fetchedAt: entry.fetchedAt,
1081
+ create: async ({ name, orgId, parentId, color, icon, slug }) => {
1082
+ if (!dimension) return { ok: false, error: "Missing dimension" };
1083
+ return store.createCategory({
1084
+ dimension,
1085
+ name,
1086
+ orgId,
1087
+ ...parentId !== void 0 ? { parentId } : {},
1088
+ ...color !== void 0 ? { color } : {},
1089
+ ...icon !== void 0 ? { icon } : {},
1090
+ ...slug !== void 0 ? { slug } : {}
1091
+ });
1092
+ },
1093
+ update: async ({ id, name, slug, color, icon, position }) => {
1094
+ if (!dimension) return { ok: false, error: "Missing dimension" };
1095
+ return store.updateCategory({
1096
+ dimension,
1097
+ id,
1098
+ name,
1099
+ slug,
1100
+ color,
1101
+ icon,
1102
+ position
1103
+ });
1104
+ },
1105
+ reparent: async (id, parentId) => {
1106
+ if (!dimension) return { ok: false, error: "Missing dimension" };
1107
+ return store.reparentCategory({ dimension, id, parentId });
1108
+ },
1109
+ remove: async (id) => {
1110
+ if (!dimension) return { ok: false, error: "Missing dimension" };
1111
+ return store.deleteCategory({ dimension, id });
1112
+ },
1113
+ reload: async () => {
1114
+ if (!dimension) return;
1115
+ await store.loadCategories(dimension, { force: true });
1116
+ }
1117
+ };
1118
+ }
1119
+
1120
+ // src/react/hooks/useUniversalEntitySearch.ts
1121
+ import { useEffect as useEffect5, useRef as useRef5, useState as useState3 } from "react";
1122
+ var DEBOUNCE_MS = 250;
1123
+ var RECENTS_LIMIT = 12;
1124
+ function useUniversalEntitySearch(args) {
1125
+ const {
1126
+ query,
1127
+ tokens,
1128
+ ownerId,
1129
+ enabled = true,
1130
+ perTokenLimit = 5,
1131
+ emptyQueryMode = "recents"
1132
+ } = args;
1133
+ const store = useAssociationsStore();
1134
+ const [results, setResults] = useState3([]);
1135
+ const [loading, setLoading] = useState3(false);
1136
+ const [isRecents, setIsRecents] = useState3(true);
1137
+ const runRef = useRef5(0);
1138
+ const tokensKey = (tokens ?? []).join(",");
1139
+ useEffect5(() => {
1140
+ if (!enabled) return;
1141
+ const run = ++runRef.current;
1142
+ const trimmed = query.trim();
1143
+ const timer = setTimeout(async () => {
1144
+ if (runRef.current !== run) return;
1145
+ setLoading(true);
1146
+ const activeTokens = tokensKey ? tokensKey.split(",") : store.registry.curatedTokens();
1147
+ let next = [];
1148
+ if (trimmed || emptyQueryMode === "candidates") {
1149
+ next = await store.candidates.searchCandidatesAcrossTokens({
1150
+ tokens: activeTokens,
1151
+ search: trimmed,
1152
+ ownerId: ownerId ?? null,
1153
+ perTokenLimit
1154
+ });
1155
+ } else {
1156
+ next = await loadRecents(store, activeTokens);
1157
+ }
1158
+ if (runRef.current !== run) return;
1159
+ setResults(next);
1160
+ setIsRecents(!trimmed && emptyQueryMode === "recents");
1161
+ setLoading(false);
1162
+ }, DEBOUNCE_MS);
1163
+ return () => clearTimeout(timer);
1164
+ }, [query, tokensKey, ownerId, enabled, perTokenLimit, emptyQueryMode, store]);
1165
+ return { results, loading, isRecents };
1166
+ }
1167
+ async function loadRecents(store, tokens) {
1168
+ const res = await store.favorites.list();
1169
+ if (!res.ok) return [];
1170
+ const tokenSet = new Set(tokens);
1171
+ const rows = res.data.items.filter(
1172
+ (i) => tokenSet.has(i.entityType) && isEntityTypeToken(i.entityType) && i.lastViewedAt
1173
+ ).sort((a, b) => (b.lastViewedAt ?? "").localeCompare(a.lastViewedAt ?? "")).slice(0, RECENTS_LIMIT);
1174
+ if (rows.length === 0) return [];
1175
+ const byToken = /* @__PURE__ */ new Map();
1176
+ for (const r of rows) {
1177
+ const list = byToken.get(r.entityType) ?? [];
1178
+ list.push(r.entityId);
1179
+ byToken.set(r.entityType, list);
1180
+ }
1181
+ const titles = /* @__PURE__ */ new Map();
1182
+ await Promise.all(
1183
+ [...byToken.entries()].map(async ([token, ids]) => {
1184
+ titles.set(token, await store.titles.fetch(token, ids));
1185
+ })
1186
+ );
1187
+ const out = [];
1188
+ for (const r of rows) {
1189
+ const title = titles.get(r.entityType)?.get(r.entityId);
1190
+ if (!title) continue;
1191
+ out.push({
1192
+ token: r.entityType,
1193
+ id: r.entityId,
1194
+ title
1195
+ });
1196
+ }
1197
+ return out;
1198
+ }
1199
+
1200
+ // src/react/hooks/useAssociationCandidates.ts
1201
+ import { useEffect as useEffect6, useState as useState4 } from "react";
1202
+ function useAssociationCandidates(args) {
1203
+ const { token, enabled = true, search, limit } = args;
1204
+ const store = useAssociationsStore();
1205
+ const userId = currentUserIdOrNull(store);
1206
+ const [candidates, setCandidates] = useState4([]);
1207
+ const [loading, setLoading] = useState4(false);
1208
+ const [error, setError] = useState4(null);
1209
+ const [tick, setTick] = useState4(0);
1210
+ useEffect6(() => {
1211
+ if (!enabled) return void 0;
1212
+ let cancelled = false;
1213
+ void (async () => {
1214
+ setLoading(true);
1215
+ setError(null);
1216
+ const res = await store.candidates.listAssociationCandidates({
1217
+ token,
1218
+ ownerId: userId,
1219
+ ...search !== void 0 ? { search } : {},
1220
+ ...limit !== void 0 ? { limit } : {}
1221
+ });
1222
+ if (cancelled) return;
1223
+ if (res.ok) {
1224
+ setCandidates(res.data);
1225
+ } else {
1226
+ setError(res.error);
1227
+ setCandidates([]);
1228
+ }
1229
+ setLoading(false);
1230
+ })();
1231
+ return () => {
1232
+ cancelled = true;
1233
+ };
1234
+ }, [token, enabled, userId, search, limit, tick, store]);
1235
+ return {
1236
+ candidates,
1237
+ loading,
1238
+ error,
1239
+ reload: () => setTick((t) => t + 1)
1240
+ };
1241
+ }
1242
+ function currentUserIdOrNull(store) {
1243
+ try {
1244
+ return store.identity.requireUserId();
1245
+ } catch {
1246
+ return null;
1247
+ }
1248
+ }
1249
+
1250
+ // src/react/hooks/useAssociationEntitySelect.ts
1251
+ import { useState as useState5 } from "react";
1252
+ function useAssociationEntitySelectAdapter(args) {
1253
+ const { token, container, activeId, onActiveChange, createColumns } = args;
1254
+ const store = useAssociationsStore();
1255
+ const notifier = useNotifier();
1256
+ const links = useContainerLinks({
1257
+ containerType: container.type,
1258
+ containerId: container.id,
1259
+ orgId: container.orgId ?? null
1260
+ });
1261
+ const rows = links.linksFor(token);
1262
+ const { titleFor } = useEntityTitles(
1263
+ rows.map((r) => ({ token, id: r.resourceId, label: r.label }))
1264
+ );
1265
+ const [titleOverrides, setTitleOverrides] = useState5(
1266
+ {}
1267
+ );
1268
+ const [localActiveId, setLocalActiveId] = useState5(null);
1269
+ const items = rows.map((r) => ({
1270
+ id: r.resourceId,
1271
+ title: titleOverrides[r.resourceId] ?? titleFor({ token, id: r.resourceId, label: r.label })
1272
+ }));
1273
+ const controlled = activeId !== void 0;
1274
+ const resolvedActive = controlled ? activeId : localActiveId;
1275
+ const effectiveActive = resolvedActive && items.some((i) => i.id === resolvedActive) ? resolvedActive : items[0]?.id ?? null;
1276
+ const setActive = (id) => {
1277
+ if (!controlled) setLocalActiveId(id);
1278
+ return onActiveChange?.(id);
1279
+ };
1280
+ return {
1281
+ loading: links.status === "loading" || links.status === "idle",
1282
+ items,
1283
+ activeId: effectiveActive,
1284
+ setActive,
1285
+ createAndAttach: async (title) => {
1286
+ const created = await store.entityRows.createEntityRow(token, {
1287
+ title,
1288
+ orgId: container.orgId ?? null,
1289
+ ...createColumns !== void 0 ? { extraColumns: createColumns } : {}
1290
+ });
1291
+ if (!created.ok) return null;
1292
+ let attached = await links.attach(token, created.data.id, title);
1293
+ if (!attached.ok) {
1294
+ attached = await links.attach(token, created.data.id, title);
1295
+ }
1296
+ if (!attached.ok) {
1297
+ store.errorSink({
1298
+ code: "created_but_attach_failed",
1299
+ message: "[useAssociationEntitySelectAdapter] created but attach failed",
1300
+ context: {
1301
+ token,
1302
+ id: created.data.id,
1303
+ error: attached.error
1304
+ }
1305
+ });
1306
+ notifier.error(
1307
+ `Created "${title}" but couldn't link it here \u2014 it exists in your library`
1308
+ );
1309
+ return null;
1310
+ }
1311
+ setTitleOverrides((prev) => ({ ...prev, [created.data.id]: title }));
1312
+ await setActive(created.data.id);
1313
+ return created.data.id;
1314
+ },
1315
+ rename: async (id, title) => {
1316
+ const res = await store.entityRows.renameEntityRow(token, id, title);
1317
+ if (res.ok) setTitleOverrides((prev) => ({ ...prev, [id]: title.trim() }));
1318
+ return res.ok;
1319
+ },
1320
+ detach: (id) => links.detach(token, id)
1321
+ };
1322
+ }
1323
+
1324
+ // src/react/components/PrimaryEntityContext.tsx
1325
+ import { createContext as createContext2, useContext as useContext2 } from "react";
1326
+ import { jsx as jsx2 } from "react/jsx-runtime";
1327
+ var PrimaryEntityCtx = createContext2(null);
1328
+ function PrimaryEntityProvider({
1329
+ value,
1330
+ children
1331
+ }) {
1332
+ return /* @__PURE__ */ jsx2(PrimaryEntityCtx.Provider, { value, children });
1333
+ }
1334
+ function usePrimaryEntity() {
1335
+ return useContext2(PrimaryEntityCtx);
1336
+ }
1337
+
1338
+ // src/react/components/AssociationWindow.tsx
1339
+ import { useId } from "react";
1340
+ import { cn } from "@ai-matrx/design-system";
1341
+
1342
+ // src/react/icons.tsx
1343
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
1344
+ function base(props) {
1345
+ return {
1346
+ xmlns: "http://www.w3.org/2000/svg",
1347
+ width: 24,
1348
+ height: 24,
1349
+ viewBox: "0 0 24 24",
1350
+ fill: "none",
1351
+ stroke: "currentColor",
1352
+ strokeWidth: 2,
1353
+ strokeLinecap: "round",
1354
+ strokeLinejoin: "round",
1355
+ "aria-hidden": true,
1356
+ ...props
1357
+ };
1358
+ }
1359
+ function SpinnerIcon(props) {
1360
+ return /* @__PURE__ */ jsx3("svg", { ...base(props), children: /* @__PURE__ */ jsx3("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) });
1361
+ }
1362
+ function PlusIcon(props) {
1363
+ return /* @__PURE__ */ jsxs("svg", { ...base(props), children: [
1364
+ /* @__PURE__ */ jsx3("path", { d: "M5 12h14" }),
1365
+ /* @__PURE__ */ jsx3("path", { d: "M12 5v14" })
1366
+ ] });
1367
+ }
1368
+ function XIcon(props) {
1369
+ return /* @__PURE__ */ jsxs("svg", { ...base(props), children: [
1370
+ /* @__PURE__ */ jsx3("path", { d: "M18 6 6 18" }),
1371
+ /* @__PURE__ */ jsx3("path", { d: "m6 6 12 12" })
1372
+ ] });
1373
+ }
1374
+ function CheckIcon(props) {
1375
+ return /* @__PURE__ */ jsx3("svg", { ...base(props), children: /* @__PURE__ */ jsx3("path", { d: "M20 6 9 17l-5-5" }) });
1376
+ }
1377
+ function SearchIcon(props) {
1378
+ return /* @__PURE__ */ jsxs("svg", { ...base(props), children: [
1379
+ /* @__PURE__ */ jsx3("circle", { cx: "11", cy: "11", r: "8" }),
1380
+ /* @__PURE__ */ jsx3("path", { d: "m21 21-4.3-4.3" })
1381
+ ] });
1382
+ }
1383
+ function Link2Icon(props) {
1384
+ return /* @__PURE__ */ jsxs("svg", { ...base(props), children: [
1385
+ /* @__PURE__ */ jsx3("path", { d: "M9 17H7A5 5 0 0 1 7 7h2" }),
1386
+ /* @__PURE__ */ jsx3("path", { d: "M15 7h2a5 5 0 1 1 0 10h-2" }),
1387
+ /* @__PURE__ */ jsx3("line", { x1: "8", x2: "16", y1: "12", y2: "12" })
1388
+ ] });
1389
+ }
1390
+ function RefreshIcon(props) {
1391
+ return /* @__PURE__ */ jsxs("svg", { ...base(props), children: [
1392
+ /* @__PURE__ */ jsx3("path", { d: "M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" }),
1393
+ /* @__PURE__ */ jsx3("path", { d: "M21 3v5h-5" }),
1394
+ /* @__PURE__ */ jsx3("path", { d: "M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" }),
1395
+ /* @__PURE__ */ jsx3("path", { d: "M8 16H3v5" })
1396
+ ] });
1397
+ }
1398
+ function PinIcon(props) {
1399
+ return /* @__PURE__ */ jsxs("svg", { ...base(props), children: [
1400
+ /* @__PURE__ */ jsx3("path", { d: "M12 17v5" }),
1401
+ /* @__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" })
1402
+ ] });
1403
+ }
1404
+ function UploadIcon(props) {
1405
+ return /* @__PURE__ */ jsxs("svg", { ...base(props), children: [
1406
+ /* @__PURE__ */ jsx3("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
1407
+ /* @__PURE__ */ jsx3("polyline", { points: "17 8 12 3 7 8" }),
1408
+ /* @__PURE__ */ jsx3("line", { x1: "12", x2: "12", y1: "3", y2: "15" })
1409
+ ] });
1410
+ }
1411
+ function FolderOpenIcon(props) {
1412
+ 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" }) });
1413
+ }
1414
+ function ExternalLinkIcon(props) {
1415
+ return /* @__PURE__ */ jsxs("svg", { ...base(props), children: [
1416
+ /* @__PURE__ */ jsx3("path", { d: "M15 3h6v6" }),
1417
+ /* @__PURE__ */ jsx3("path", { d: "M10 14 21 3" }),
1418
+ /* @__PURE__ */ jsx3("path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" })
1419
+ ] });
1420
+ }
1421
+ function DefaultEntityIcon(props) {
1422
+ return /* @__PURE__ */ jsxs("svg", { ...base(props), children: [
1423
+ /* @__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" }),
1424
+ /* @__PURE__ */ jsx3("path", { d: "m7 16.5-4.74-2.85" }),
1425
+ /* @__PURE__ */ jsx3("path", { d: "m7 16.5 5-3" }),
1426
+ /* @__PURE__ */ jsx3("path", { d: "M7 16.5v5.17" }),
1427
+ /* @__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" }),
1428
+ /* @__PURE__ */ jsx3("path", { d: "m17 16.5-5-3" }),
1429
+ /* @__PURE__ */ jsx3("path", { d: "m17 16.5 4.74-2.85" }),
1430
+ /* @__PURE__ */ jsx3("path", { d: "M17 16.5v5.17" }),
1431
+ /* @__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" }),
1432
+ /* @__PURE__ */ jsx3("path", { d: "M12 8 7.26 5.15" }),
1433
+ /* @__PURE__ */ jsx3("path", { d: "m12 8 4.74-2.85" }),
1434
+ /* @__PURE__ */ jsx3("path", { d: "M12 13.5V8" })
1435
+ ] });
1436
+ }
1437
+
1438
+ // src/react/components/AssociationWindow.tsx
1439
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
1440
+ function AssociationWindow({
1441
+ open,
1442
+ onClose,
1443
+ scopeId,
1444
+ title,
1445
+ icon,
1446
+ subtitle,
1447
+ children
1448
+ }) {
1449
+ const instanceId = useId();
1450
+ const { windowShell } = useAssociationsUiPorts();
1451
+ if (!open) return null;
1452
+ const body = /* @__PURE__ */ jsxs2("div", { className: "flex h-full min-h-0 flex-col gap-2 p-3", children: [
1453
+ subtitle ? /* @__PURE__ */ jsx4("div", { className: "shrink-0 text-xs text-muted-foreground", children: subtitle }) : null,
1454
+ children
1455
+ ] });
1456
+ if (windowShell) {
1457
+ const HostWindow = windowShell.Window;
1458
+ return /* @__PURE__ */ jsx4(
1459
+ HostWindow,
1460
+ {
1461
+ id: `association:${scopeId}:${instanceId}`,
1462
+ title,
1463
+ onClose,
1464
+ children: body
1465
+ }
1466
+ );
1467
+ }
1468
+ return /* @__PURE__ */ jsxs2(
1469
+ "div",
1470
+ {
1471
+ role: "dialog",
1472
+ "aria-label": title,
1473
+ className: cn(
1474
+ "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"
1475
+ ),
1476
+ style: { height: "min(580px, 85dvh)" },
1477
+ children: [
1478
+ /* @__PURE__ */ jsxs2("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-border px-3 py-2", children: [
1479
+ /* @__PURE__ */ jsxs2("span", { className: "flex min-w-0 items-center gap-1.5 text-sm font-medium text-foreground", children: [
1480
+ icon,
1481
+ /* @__PURE__ */ jsx4("span", { className: "truncate", children: title })
1482
+ ] }),
1483
+ /* @__PURE__ */ jsx4(
1484
+ "button",
1485
+ {
1486
+ type: "button",
1487
+ onClick: onClose,
1488
+ "aria-label": "Close",
1489
+ 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",
1490
+ children: /* @__PURE__ */ jsx4(XIcon, { className: "h-3.5 w-3.5" })
1491
+ }
1492
+ )
1493
+ ] }),
1494
+ /* @__PURE__ */ jsx4("div", { className: "min-h-0 flex-1", children: body })
1495
+ ]
1496
+ }
1497
+ );
1498
+ }
1499
+
1500
+ // src/react/components/AssociationPicker.tsx
1501
+ import { useState as useState6 } from "react";
1502
+ import { cn as cn2, Input } from "@ai-matrx/design-system";
1503
+ import { Fragment, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
1504
+ function AssociationPicker(props) {
1505
+ const store = useAssociationsStore();
1506
+ const { pickerOverrides } = useAssociationsUiPorts();
1507
+ const info = store.registry.getEntityInfo(props.token);
1508
+ const Icon = info.Icon ?? DefaultEntityIcon;
1509
+ const Override = pickerOverrides?.[props.token];
1510
+ if (Override) return /* @__PURE__ */ jsx5(Override, { ...props });
1511
+ const title = `Add ${info.labelPlural}`;
1512
+ const subtitle = props.containerLabel ? `Attach to ${props.containerLabel}` : "Click an item to attach or detach it";
1513
+ if (!props.open) return null;
1514
+ return /* @__PURE__ */ jsx5(
1515
+ AssociationWindow,
1516
+ {
1517
+ open: props.open,
1518
+ onClose: () => props.onOpenChange(false),
1519
+ scopeId: `picker:${props.token}`,
1520
+ title,
1521
+ icon: /* @__PURE__ */ jsx5(Icon, { className: "size-3.5 text-primary" }),
1522
+ subtitle,
1523
+ children: /* @__PURE__ */ jsx5(
1524
+ AssociationCandidateBody,
1525
+ {
1526
+ token: props.token,
1527
+ enabled: props.open,
1528
+ orgId: props.orgId,
1529
+ attachedIds: props.attachedIds,
1530
+ onAttach: props.onAttach,
1531
+ onDetach: props.onDetach,
1532
+ onClose: () => props.onOpenChange(false)
1533
+ }
1534
+ )
1535
+ }
1536
+ );
1537
+ }
1538
+ function AssociationCandidateBody({
1539
+ token,
1540
+ enabled,
1541
+ orgId,
1542
+ attachedIds,
1543
+ onAttach,
1544
+ onDetach,
1545
+ onClose
1546
+ }) {
1547
+ void onClose;
1548
+ return /* @__PURE__ */ jsx5(
1549
+ EntityCandidateList,
1550
+ {
1551
+ token,
1552
+ enabled,
1553
+ orgId,
1554
+ attachedIds,
1555
+ onAttach,
1556
+ onDetach
1557
+ }
1558
+ );
1559
+ }
1560
+ function EntityCandidateList({
1561
+ token,
1562
+ enabled,
1563
+ orgId,
1564
+ attachedIds,
1565
+ onAttach,
1566
+ onDetach
1567
+ }) {
1568
+ const store = useAssociationsStore();
1569
+ const notifier = useNotifier();
1570
+ const [search, setSearch] = useState6("");
1571
+ const [busyId, setBusyId] = useState6(null);
1572
+ const info = store.registry.getEntityInfo(token);
1573
+ const Icon = info.Icon ?? DefaultEntityIcon;
1574
+ const { candidates, loading, error, reload } = useAssociationCandidates({
1575
+ token,
1576
+ enabled,
1577
+ ...search.trim() ? { search: search.trim() } : {}
1578
+ });
1579
+ const canCreate = info.titleColumn !== null && info.listCandidates === null;
1580
+ const toggle = async (id, title) => {
1581
+ if (busyId) return;
1582
+ setBusyId(id);
1583
+ try {
1584
+ const attached = attachedIds.has(id);
1585
+ const res = attached ? await onDetach(id) : await onAttach(id, title);
1586
+ if (!res.ok) {
1587
+ notifier.error(
1588
+ `Couldn't ${attached ? "detach" : "attach"} "${title}"` + (res.error ? `: ${res.error}` : "")
1589
+ );
1590
+ }
1591
+ } finally {
1592
+ setBusyId(null);
1593
+ }
1594
+ };
1595
+ return /* @__PURE__ */ jsxs3(Fragment, { children: [
1596
+ /* @__PURE__ */ jsxs3("div", { className: "relative mb-2", children: [
1597
+ /* @__PURE__ */ jsx5(SearchIcon, { className: "absolute left-2.5 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" }),
1598
+ /* @__PURE__ */ jsx5(
1599
+ Input,
1600
+ {
1601
+ value: search,
1602
+ onChange: (e) => setSearch(e.target.value),
1603
+ placeholder: `Search ${info.labelPlural.toLowerCase()}\u2026`,
1604
+ className: "h-11 pl-8 text-base md:h-9",
1605
+ style: { fontSize: 16 }
1606
+ }
1607
+ )
1608
+ ] }),
1609
+ /* @__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: [
1610
+ /* @__PURE__ */ jsx5(SpinnerIcon, { className: "h-4 w-4 animate-spin" }),
1611
+ "Loading\u2026"
1612
+ ] }) : error ? /* @__PURE__ */ jsxs3("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-[12px] text-destructive", children: [
1613
+ /* @__PURE__ */ jsx5("p", { className: "font-medium", children: "Couldn\u2019t load items" }),
1614
+ /* @__PURE__ */ jsx5("p", { className: "opacity-80", children: error }),
1615
+ /* @__PURE__ */ jsx5(
1616
+ "button",
1617
+ {
1618
+ type: "button",
1619
+ onClick: reload,
1620
+ className: "mt-1 underline hover:no-underline",
1621
+ children: "Retry"
1622
+ }
1623
+ )
1624
+ ] }) : 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) => {
1625
+ const attached = attachedIds.has(c.id);
1626
+ const busy = busyId === c.id;
1627
+ return /* @__PURE__ */ jsx5("li", { children: /* @__PURE__ */ jsxs3(
1628
+ "button",
1629
+ {
1630
+ type: "button",
1631
+ disabled: busy,
1632
+ onClick: () => toggle(c.id, c.title),
1633
+ className: cn2(
1634
+ "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",
1635
+ "hover:bg-accent disabled:opacity-50",
1636
+ attached && "bg-accent/40"
1637
+ ),
1638
+ children: [
1639
+ /* @__PURE__ */ jsx5(Icon, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
1640
+ /* @__PURE__ */ jsx5("span", { className: "flex-1 min-w-0 truncate text-foreground", children: c.title }),
1641
+ /* @__PURE__ */ jsx5(
1642
+ "span",
1643
+ {
1644
+ className: cn2(
1645
+ "flex h-5 w-5 items-center justify-center rounded-full shrink-0",
1646
+ attached ? "bg-primary text-primary-foreground" : "border border-border text-muted-foreground group-hover:border-primary/60"
1647
+ ),
1648
+ 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" })
1649
+ }
1650
+ )
1651
+ ]
1652
+ }
1653
+ ) }, c.id);
1654
+ }) }) }),
1655
+ canCreate && /* @__PURE__ */ jsx5(
1656
+ CreateAndAttachFooter,
1657
+ {
1658
+ token,
1659
+ orgId,
1660
+ seed: search.trim(),
1661
+ onAttach,
1662
+ onCreated: reload
1663
+ }
1664
+ )
1665
+ ] });
1666
+ }
1667
+ function CreateAndAttachFooter({
1668
+ token,
1669
+ orgId,
1670
+ seed,
1671
+ onAttach,
1672
+ onCreated
1673
+ }) {
1674
+ const store = useAssociationsStore();
1675
+ const notifier = useNotifier();
1676
+ const info = store.registry.getEntityInfo(token);
1677
+ const [editing, setEditing] = useState6(false);
1678
+ const [name, setName] = useState6("");
1679
+ const [busy, setBusy] = useState6(false);
1680
+ const submit = async () => {
1681
+ const title = name.trim();
1682
+ if (!title || busy) return;
1683
+ setBusy(true);
1684
+ try {
1685
+ const created = await store.entityRows.createEntityRow(token, {
1686
+ title,
1687
+ orgId: orgId ?? null
1688
+ });
1689
+ if (!created.ok) {
1690
+ notifier.error(
1691
+ `Couldn't create ${info.label.toLowerCase()}: ${created.error}`
1692
+ );
1693
+ return;
1694
+ }
1695
+ let attached = await onAttach(created.data.id, created.data.title);
1696
+ if (!attached.ok) {
1697
+ attached = await onAttach(created.data.id, created.data.title);
1698
+ }
1699
+ if (attached.ok) {
1700
+ notifier.success(`"${title}" created and attached`);
1701
+ } else {
1702
+ notifier.error(
1703
+ `Created "${title}" but couldn't attach it \u2014 it's saved in your ${info.labelPlural.toLowerCase()}; pick it from the list to retry.`
1704
+ );
1705
+ }
1706
+ setEditing(false);
1707
+ setName("");
1708
+ onCreated();
1709
+ } finally {
1710
+ setBusy(false);
1711
+ }
1712
+ };
1713
+ if (!editing) {
1714
+ return /* @__PURE__ */ jsxs3(
1715
+ "button",
1716
+ {
1717
+ type: "button",
1718
+ onClick: () => {
1719
+ setName(seed);
1720
+ setEditing(true);
1721
+ },
1722
+ 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",
1723
+ children: [
1724
+ /* @__PURE__ */ jsx5(PlusIcon, { className: "h-4 w-4" }),
1725
+ "New ",
1726
+ info.label,
1727
+ seed ? /* @__PURE__ */ jsxs3("span", { className: "max-w-40 truncate text-muted-foreground/70", children: [
1728
+ "\u201C",
1729
+ seed,
1730
+ "\u201D"
1731
+ ] }) : null
1732
+ ]
1733
+ }
1734
+ );
1735
+ }
1736
+ return /* @__PURE__ */ jsxs3("div", { className: "mt-2 flex shrink-0 items-center gap-1.5", children: [
1737
+ /* @__PURE__ */ jsx5(
1738
+ Input,
1739
+ {
1740
+ autoFocus: true,
1741
+ value: name,
1742
+ onChange: (e) => setName(e.target.value),
1743
+ onKeyDown: (e) => {
1744
+ if (e.key === "Enter") void submit();
1745
+ if (e.key === "Escape") {
1746
+ setEditing(false);
1747
+ setName("");
1748
+ }
1749
+ },
1750
+ placeholder: `New ${info.label.toLowerCase()} name\u2026`,
1751
+ disabled: busy,
1752
+ className: "h-11 flex-1 text-base md:h-8",
1753
+ style: { fontSize: 16 }
1754
+ }
1755
+ ),
1756
+ /* @__PURE__ */ jsx5(
1757
+ "button",
1758
+ {
1759
+ type: "button",
1760
+ disabled: busy || !name.trim(),
1761
+ onClick: () => void submit(),
1762
+ 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",
1763
+ children: busy ? /* @__PURE__ */ jsx5(SpinnerIcon, { className: "h-3.5 w-3.5 animate-spin" }) : "Create"
1764
+ }
1765
+ ),
1766
+ /* @__PURE__ */ jsx5(
1767
+ "button",
1768
+ {
1769
+ type: "button",
1770
+ disabled: busy,
1771
+ onClick: () => {
1772
+ setEditing(false);
1773
+ setName("");
1774
+ },
1775
+ title: "Cancel",
1776
+ 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",
1777
+ children: /* @__PURE__ */ jsx5(XIcon, { className: "h-4 w-4" })
1778
+ }
1779
+ )
1780
+ ] });
1781
+ }
1782
+ function ListMessage({ children }) {
1783
+ return /* @__PURE__ */ jsx5("div", { className: "flex items-center justify-center gap-2 py-8 text-[13px] text-muted-foreground", children });
1784
+ }
1785
+
1786
+ // src/react/components/UniversalAssociationPicker.tsx
1787
+ import { useState as useState7 } from "react";
1788
+ import { cn as cn3, Input as Input2 } from "@ai-matrx/design-system";
1789
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
1790
+ function attachedKey(token, id) {
1791
+ return `${token}:${id}`;
1792
+ }
1793
+ function UniversalAssociationPicker(props) {
1794
+ const { attachedKeys, onAttach, onDetach, ownerId, orgId, className } = props;
1795
+ const store = useAssociationsStore();
1796
+ const { entityDoors, pickerOverrides } = useAssociationsUiPorts();
1797
+ const notifier = useNotifier();
1798
+ const tokens = props.tokens ?? store.registry.curatedTokens();
1799
+ const [query, setQuery] = useState7("");
1800
+ const [browseToken, setBrowseToken] = useState7(null);
1801
+ const [busyKey, setBusyKey] = useState7(null);
1802
+ const { results, loading, isRecents } = useUniversalEntitySearch({
1803
+ query,
1804
+ tokens,
1805
+ ownerId: ownerId ?? null,
1806
+ enabled: browseToken === null
1807
+ });
1808
+ const DoorControls = entityDoors?.DoorControls;
1809
+ const toggle = async (c) => {
1810
+ const key = attachedKey(c.token, c.id);
1811
+ if (busyKey) return;
1812
+ setBusyKey(key);
1813
+ try {
1814
+ const attached = attachedKeys.has(key);
1815
+ const res = attached ? await onDetach(c.token, c.id) : await onAttach(c.token, c.id, c.title);
1816
+ if (!res.ok) {
1817
+ notifier.error(
1818
+ `Couldn't ${attached ? "detach" : "attach"} "${c.title}"` + (res.error ? `: ${res.error}` : "")
1819
+ );
1820
+ }
1821
+ } finally {
1822
+ setBusyKey(null);
1823
+ }
1824
+ };
1825
+ const groups = /* @__PURE__ */ new Map();
1826
+ for (const r of results) {
1827
+ const list = groups.get(r.token) ?? [];
1828
+ list.push(r);
1829
+ groups.set(r.token, list);
1830
+ }
1831
+ const BrowseOverride = browseToken ? pickerOverrides?.[browseToken] : void 0;
1832
+ return /* @__PURE__ */ jsxs4("div", { className: cn3("flex min-h-0 flex-1 flex-col gap-2", className), children: [
1833
+ /* @__PURE__ */ jsxs4("div", { className: "relative", children: [
1834
+ /* @__PURE__ */ jsx6(SearchIcon, { className: "absolute left-2.5 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }),
1835
+ /* @__PURE__ */ jsx6(
1836
+ Input2,
1837
+ {
1838
+ value: query,
1839
+ onChange: (e) => {
1840
+ setQuery(e.target.value);
1841
+ if (browseToken) setBrowseToken(null);
1842
+ },
1843
+ placeholder: "Search everything\u2026",
1844
+ className: "pl-8 text-base",
1845
+ style: { fontSize: 16 }
1846
+ }
1847
+ ),
1848
+ 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" })
1849
+ ] }),
1850
+ /* @__PURE__ */ jsx6("div", { className: "flex flex-wrap gap-1", children: tokens.map((t) => {
1851
+ const info = store.registry.getEntityInfo(t);
1852
+ const ChipIcon = info.Icon ?? DefaultEntityIcon;
1853
+ const active = browseToken === t;
1854
+ return /* @__PURE__ */ jsxs4(
1855
+ "button",
1856
+ {
1857
+ type: "button",
1858
+ onClick: () => setBrowseToken(active ? null : t),
1859
+ className: cn3(
1860
+ "inline-flex items-center gap-1 rounded-md border px-1.5 py-0.5 text-[11px] transition-colors",
1861
+ active ? "border-primary/50 bg-primary/10 text-foreground" : "border-border text-muted-foreground hover:text-foreground hover:bg-accent"
1862
+ ),
1863
+ children: [
1864
+ /* @__PURE__ */ jsx6(ChipIcon, { className: "h-3 w-3" }),
1865
+ info.labelPlural
1866
+ ]
1867
+ },
1868
+ t
1869
+ );
1870
+ }) }),
1871
+ browseToken && BrowseOverride ? (
1872
+ // The token's canonical host picker (e.g. the stored-files browser)
1873
+ // owns per-token browsing when registered — never a plain list twin.
1874
+ /* @__PURE__ */ jsx6(
1875
+ BrowseOverride,
1876
+ {
1877
+ open: true,
1878
+ onOpenChange: (open) => {
1879
+ if (!open) setBrowseToken(null);
1880
+ },
1881
+ token: browseToken,
1882
+ orgId: orgId ?? null,
1883
+ attachedIds: new Set(
1884
+ [...attachedKeys].filter((k) => k.startsWith(`${browseToken}:`)).map((k) => k.slice(browseToken.length + 1))
1885
+ ),
1886
+ onAttach: (id, title) => onAttach(browseToken, id, title),
1887
+ onDetach: (id) => onDetach(browseToken, id)
1888
+ }
1889
+ )
1890
+ ) : browseToken ? /* @__PURE__ */ jsx6("div", { className: "flex min-h-0 flex-1 flex-col", children: /* @__PURE__ */ jsx6(
1891
+ AssociationCandidateBody,
1892
+ {
1893
+ token: browseToken,
1894
+ enabled: true,
1895
+ orgId,
1896
+ onClose: () => setBrowseToken(null),
1897
+ attachedIds: new Set(
1898
+ [...attachedKeys].filter((k) => k.startsWith(`${browseToken}:`)).map((k) => k.slice(browseToken.length + 1))
1899
+ ),
1900
+ onAttach: (id, title) => onAttach(browseToken, id, title),
1901
+ onDetach: (id) => onDetach(browseToken, id)
1902
+ }
1903
+ ) }) : /* @__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: [
1904
+ isRecents && /* @__PURE__ */ jsx6("p", { className: "px-1 text-[10px] font-medium uppercase tracking-wider text-muted-foreground/60", children: "Recent" }),
1905
+ [...groups.entries()].map(([token, items]) => {
1906
+ const info = store.registry.getEntityInfo(token);
1907
+ const GroupIcon = info.Icon ?? DefaultEntityIcon;
1908
+ return /* @__PURE__ */ jsxs4("div", { className: "space-y-0.5", children: [
1909
+ /* @__PURE__ */ jsxs4("p", { className: "flex items-center gap-1 px-1 text-[10px] font-medium uppercase tracking-wider text-muted-foreground/70", children: [
1910
+ /* @__PURE__ */ jsx6(GroupIcon, { className: "h-3 w-3" }),
1911
+ info.labelPlural
1912
+ ] }),
1913
+ /* @__PURE__ */ jsx6("ul", { className: "space-y-0.5", children: items.map((c) => {
1914
+ const key = attachedKey(c.token, c.id);
1915
+ const attached = attachedKeys.has(key);
1916
+ const busy = busyKey === key;
1917
+ return /* @__PURE__ */ jsxs4(
1918
+ "li",
1919
+ {
1920
+ className: cn3(
1921
+ "group/entity-ref group flex items-center gap-1 rounded-md pr-1.5 transition-colors",
1922
+ "hover:bg-accent",
1923
+ attached && "bg-accent/40"
1924
+ ),
1925
+ children: [
1926
+ /* @__PURE__ */ jsxs4(
1927
+ "button",
1928
+ {
1929
+ type: "button",
1930
+ disabled: busy,
1931
+ onClick: () => toggle(c),
1932
+ title: attached ? `Detach "${c.title}"` : `Attach "${c.title}"`,
1933
+ className: cn3(
1934
+ "flex min-w-0 flex-1 items-center gap-2 rounded-md px-2.5 py-1.5 text-left text-sm",
1935
+ "disabled:opacity-50"
1936
+ ),
1937
+ children: [
1938
+ /* @__PURE__ */ jsx6("span", { className: "min-w-0 flex-1 truncate text-foreground", children: c.title }),
1939
+ /* @__PURE__ */ jsx6(
1940
+ "span",
1941
+ {
1942
+ className: cn3(
1943
+ "flex h-5 w-5 shrink-0 items-center justify-center rounded-full",
1944
+ attached ? "bg-primary text-primary-foreground" : "border border-border text-muted-foreground group-hover:border-primary/60"
1945
+ ),
1946
+ 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" })
1947
+ }
1948
+ )
1949
+ ]
1950
+ }
1951
+ ),
1952
+ DoorControls ? /* @__PURE__ */ jsx6(
1953
+ DoorControls,
1954
+ {
1955
+ token: c.token,
1956
+ id: c.id,
1957
+ name: c.title
1958
+ }
1959
+ ) : null
1960
+ ]
1961
+ },
1962
+ key
1963
+ );
1964
+ }) })
1965
+ ] }, token);
1966
+ })
1967
+ ] }) })
1968
+ ] });
1969
+ }
1970
+
1971
+ // src/react/components/AttachedItemsSheet.tsx
1972
+ import { useEffect as useEffect7, useState as useState8 } from "react";
1973
+ import { cn as cn5 } from "@ai-matrx/design-system";
1974
+
1975
+ // src/react/components/entityDoors.tsx
1976
+ import { cn as cn4 } from "@ai-matrx/design-system";
1977
+ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
1978
+ function DoorRef(props) {
1979
+ const { onOpen, ...refProps } = props;
1980
+ const { entityDoors } = useAssociationsUiPorts();
1981
+ const store = useAssociationsStore();
1982
+ const HostRef = entityDoors?.EntityRef;
1983
+ if (HostRef) return /* @__PURE__ */ jsx7(HostRef, { ...refProps });
1984
+ const name = refProps.name ?? null;
1985
+ const href = store.registry.tryGetEntityInfo(refProps.token)?.hrefFor?.(refProps.id) ?? null;
1986
+ if (onOpen) {
1987
+ return /* @__PURE__ */ jsx7(
1988
+ "button",
1989
+ {
1990
+ type: "button",
1991
+ onClick: onOpen,
1992
+ className: cn4(
1993
+ "min-w-0 truncate text-left underline-offset-2 hover:underline",
1994
+ refProps.className
1995
+ ),
1996
+ children: name
1997
+ }
1998
+ );
1999
+ }
2000
+ if (href) {
2001
+ return /* @__PURE__ */ jsxs5(
2002
+ "a",
2003
+ {
2004
+ href,
2005
+ target: refProps.openInNewTab === false ? void 0 : "_blank",
2006
+ rel: "noopener noreferrer",
2007
+ className: cn4(
2008
+ "inline-flex min-w-0 items-center gap-1 truncate underline-offset-2 hover:underline",
2009
+ refProps.className
2010
+ ),
2011
+ children: [
2012
+ /* @__PURE__ */ jsx7("span", { className: "min-w-0 truncate", children: name }),
2013
+ /* @__PURE__ */ jsx7(ExternalLinkIcon, { className: "h-3 w-3 shrink-0 opacity-0 transition-opacity group-hover:opacity-60" })
2014
+ ]
2015
+ }
2016
+ );
2017
+ }
2018
+ return /* @__PURE__ */ jsx7("span", { className: cn4("min-w-0 truncate", refProps.className), children: name });
2019
+ }
2020
+ function UnresolvedRef({ token, id }) {
2021
+ const { entityDoors } = useAssociationsUiPorts();
2022
+ const HostUnresolved = entityDoors?.UnresolvedRef;
2023
+ if (HostUnresolved) return /* @__PURE__ */ jsx7(HostUnresolved, { token, id });
2024
+ return /* @__PURE__ */ jsx7(
2025
+ "span",
2026
+ {
2027
+ className: "inline-flex items-center rounded border border-border/60 bg-muted/40 px-1.5 py-0.5 text-[11px] text-muted-foreground",
2028
+ title: `This ${token} could not be read \u2014 it may be deleted or not shared with you.`,
2029
+ children: "Unavailable"
2030
+ }
2031
+ );
2032
+ }
2033
+
2034
+ // src/react/components/AttachedItemsSheet.tsx
2035
+ import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
2036
+ function AttachedItemsSheet(props) {
2037
+ const store = useAssociationsStore();
2038
+ const info = store.registry.getEntityInfo(props.token);
2039
+ const Icon = info.Icon ?? DefaultEntityIcon;
2040
+ const title = info.labelPlural;
2041
+ const containerName = props.container?.label ?? props.containerLabel;
2042
+ const subtitle = props.container ? /* @__PURE__ */ jsxs6("span", { className: "inline-flex min-w-0 max-w-full items-center gap-1", children: [
2043
+ "Attached to",
2044
+ /* @__PURE__ */ jsx8(
2045
+ DoorRef,
2046
+ {
2047
+ token: props.container.type,
2048
+ id: props.container.id,
2049
+ name: containerName ?? null,
2050
+ alwaysShowActions: true
2051
+ }
2052
+ )
2053
+ ] }) : props.containerLabel ? `Attached to ${props.containerLabel}` : "Attached items";
2054
+ if (!props.open) return null;
2055
+ return /* @__PURE__ */ jsx8(
2056
+ AssociationWindow,
2057
+ {
2058
+ open: props.open,
2059
+ onClose: () => props.onOpenChange(false),
2060
+ scopeId: `attached:${props.token}`,
2061
+ title,
2062
+ icon: /* @__PURE__ */ jsx8(Icon, { className: "size-3.5 text-primary" }),
2063
+ subtitle,
2064
+ children: /* @__PURE__ */ jsx8(
2065
+ AttachedItemsBody,
2066
+ {
2067
+ token: props.token,
2068
+ enabled: props.open,
2069
+ links: props.links,
2070
+ ...props.onAdd ? { onAdd: props.onAdd } : {},
2071
+ onDetach: props.onDetach
2072
+ }
2073
+ )
2074
+ }
2075
+ );
2076
+ }
2077
+ function AttachedItemsBody({
2078
+ token,
2079
+ enabled,
2080
+ links,
2081
+ onAdd,
2082
+ onDetach
2083
+ }) {
2084
+ const store = useAssociationsStore();
2085
+ const notifier = useNotifier();
2086
+ const info = store.registry.getEntityInfo(token);
2087
+ const Icon = info.Icon ?? DefaultEntityIcon;
2088
+ const [titles, setTitles] = useState8(null);
2089
+ const [titleError, setTitleError] = useState8(null);
2090
+ const [busyId, setBusyId] = useState8(null);
2091
+ const idKey = links.map((l) => l.resourceId).sort().join(",");
2092
+ useEffect7(() => {
2093
+ if (!enabled) return;
2094
+ const ids = idKey ? idKey.split(",") : [];
2095
+ if (ids.length === 0) {
2096
+ setTitles(/* @__PURE__ */ new Map());
2097
+ return;
2098
+ }
2099
+ let cancelled = false;
2100
+ void (async () => {
2101
+ try {
2102
+ const resolved = await store.titles.fetch(token, ids);
2103
+ if (!cancelled) {
2104
+ setTitles(resolved);
2105
+ setTitleError(null);
2106
+ }
2107
+ } catch (cause) {
2108
+ if (cancelled) return;
2109
+ store.errorSink({
2110
+ code: "title_resolution_failed",
2111
+ message: "[AttachedItemsSheet] title resolution failed",
2112
+ context: { token, detail: cause }
2113
+ });
2114
+ setTitles(/* @__PURE__ */ new Map());
2115
+ setTitleError(
2116
+ cause instanceof Error ? cause.message : "Could not resolve names"
2117
+ );
2118
+ }
2119
+ })();
2120
+ return () => {
2121
+ cancelled = true;
2122
+ };
2123
+ }, [token, idKey, enabled, store]);
2124
+ const detach = async (resourceId) => {
2125
+ if (busyId) return;
2126
+ setBusyId(resourceId);
2127
+ try {
2128
+ const res = await onDetach(resourceId);
2129
+ if (!res.ok) {
2130
+ notifier.error(`Couldn't detach${res.error ? `: ${res.error}` : ""}`);
2131
+ }
2132
+ } finally {
2133
+ setBusyId(null);
2134
+ }
2135
+ };
2136
+ return /* @__PURE__ */ jsxs6(Fragment2, { children: [
2137
+ 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: [
2138
+ "Showing names recorded at attach time \u2014 they may be out of date.",
2139
+ " ",
2140
+ titleError
2141
+ ] }) : null,
2142
+ /* @__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: [
2143
+ /* @__PURE__ */ jsx8(SpinnerIcon, { className: "h-4 w-4 animate-spin" }),
2144
+ "Loading\u2026"
2145
+ ] }) : /* @__PURE__ */ jsx8("ul", { className: "space-y-0.5", children: links.map((link) => {
2146
+ const resolved = titles.get(link.resourceId);
2147
+ const missing = !titles.has(link.resourceId);
2148
+ const name = resolved ?? link.label ?? (missing ? null : "Untitled");
2149
+ const busy = busyId === link.resourceId;
2150
+ return /* @__PURE__ */ jsxs6(
2151
+ "li",
2152
+ {
2153
+ className: "group/entity-ref group flex items-center gap-2 rounded-md px-2.5 py-2 text-sm transition-colors hover:bg-accent",
2154
+ children: [
2155
+ /* @__PURE__ */ jsx8(Icon, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
2156
+ /* @__PURE__ */ jsx8("span", { className: "flex-1 min-w-0 truncate", children: name === null ? (
2157
+ // The X beside the row stays the one-click detach.
2158
+ /* @__PURE__ */ jsx8(UnresolvedRef, { token, id: link.resourceId })
2159
+ ) : /* @__PURE__ */ jsx8(
2160
+ DoorRef,
2161
+ {
2162
+ token,
2163
+ id: link.resourceId,
2164
+ name,
2165
+ showIcon: false,
2166
+ className: "text-foreground"
2167
+ }
2168
+ ) }),
2169
+ /* @__PURE__ */ jsx8(
2170
+ "button",
2171
+ {
2172
+ type: "button",
2173
+ disabled: busy,
2174
+ onClick: () => void detach(link.resourceId),
2175
+ title: "Detach",
2176
+ className: cn5(
2177
+ "flex h-6 w-6 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors",
2178
+ "hover:bg-destructive/10 hover:text-destructive disabled:opacity-50"
2179
+ ),
2180
+ children: busy ? /* @__PURE__ */ jsx8(SpinnerIcon, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsx8(XIcon, { className: "h-3.5 w-3.5" })
2181
+ }
2182
+ )
2183
+ ]
2184
+ },
2185
+ link.edgeId
2186
+ );
2187
+ }) }) }),
2188
+ onAdd ? /* @__PURE__ */ jsxs6(
2189
+ "button",
2190
+ {
2191
+ type: "button",
2192
+ onClick: onAdd,
2193
+ 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",
2194
+ children: [
2195
+ /* @__PURE__ */ jsx8(PlusIcon, { className: "h-4 w-4" }),
2196
+ "Attach ",
2197
+ info.labelPlural.toLowerCase()
2198
+ ]
2199
+ }
2200
+ ) : null
2201
+ ] });
2202
+ }
2203
+
2204
+ // src/react/components/AssociationCard.tsx
2205
+ import { useState as useState9 } from "react";
2206
+ import { cn as cn6 } from "@ai-matrx/design-system";
2207
+
2208
+ // src/react/contentRoles.ts
2209
+ var CONTENT_ROLES = [
2210
+ {
2211
+ id: "utility",
2212
+ title: "Utilities",
2213
+ tagline: "The agents and tools that act on your knowledge.",
2214
+ accentText: "text-violet-600 dark:text-violet-400",
2215
+ accentBg: "bg-violet-500/10",
2216
+ accentBar: "bg-violet-500"
2217
+ },
2218
+ {
2219
+ id: "source",
2220
+ title: "Sources",
2221
+ tagline: "Incoming sources of truth (Knowledge In).",
2222
+ accentText: "text-sky-600 dark:text-sky-400",
2223
+ accentBg: "bg-sky-500/10",
2224
+ accentBar: "bg-sky-500"
2225
+ },
2226
+ {
2227
+ id: "destination",
2228
+ title: "Outputs",
2229
+ tagline: "Knowledge your team produces.",
2230
+ accentText: "text-emerald-600 dark:text-emerald-400",
2231
+ accentBg: "bg-emerald-500/10",
2232
+ accentBar: "bg-emerald-500"
2233
+ },
2234
+ {
2235
+ id: "hybrid",
2236
+ title: "Sources & Outputs",
2237
+ tagline: "Read from and written to.",
2238
+ accentText: "text-teal-600 dark:text-teal-400",
2239
+ accentBg: "bg-teal-500/10",
2240
+ accentBar: "bg-gradient-to-r from-sky-500 to-emerald-500"
2241
+ },
2242
+ {
2243
+ id: "container",
2244
+ title: "Workspaces",
2245
+ tagline: "How work is organized.",
2246
+ accentText: "text-amber-600 dark:text-amber-400",
2247
+ accentBg: "bg-amber-500/10",
2248
+ accentBar: "bg-amber-500"
2249
+ }
2250
+ ];
2251
+ function getContentRoleMeta(role) {
2252
+ return CONTENT_ROLES.find((r) => r.id === role) ?? CONTENT_ROLES[2];
2253
+ }
2254
+
2255
+ // src/react/components/AssociationCard.tsx
2256
+ import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
2257
+ function AssociationCard({
2258
+ token,
2259
+ container: containerProp,
2260
+ className
2261
+ }) {
2262
+ const store = useAssociationsStore();
2263
+ const fromCtx = usePrimaryEntity();
2264
+ const container = containerProp ?? fromCtx;
2265
+ const [open, setOpen] = useState9(false);
2266
+ const info = store.registry.getEntityInfo(token);
2267
+ const role = getContentRoleMeta(info.contentRole);
2268
+ const Icon = info.Icon ?? DefaultEntityIcon;
2269
+ const [listOpen, setListOpen] = useState9(false);
2270
+ const { status, countFor, attachedIdsFor, linksFor, attach, detach } = useContainerLinks({
2271
+ containerType: container?.type ?? "organization",
2272
+ containerId: container?.id ?? null,
2273
+ orgId: container?.orgId ?? null
2274
+ });
2275
+ if (!container) {
2276
+ store.errorSink({
2277
+ code: "association_card_no_container",
2278
+ message: `[AssociationCard] no primary entity for token "${token}" \u2014 wrap in <PrimaryEntityProvider> or pass a container prop`,
2279
+ context: { token }
2280
+ });
2281
+ return null;
2282
+ }
2283
+ const count = countFor(token);
2284
+ const loading = status === "loading" || status === "idle";
2285
+ const canAttach = info.canListCandidates;
2286
+ const canDrillIn = count > 0;
2287
+ return /* @__PURE__ */ jsxs7(Fragment3, { children: [
2288
+ /* @__PURE__ */ jsxs7(
2289
+ "div",
2290
+ {
2291
+ className: cn6(
2292
+ "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",
2293
+ className
2294
+ ),
2295
+ children: [
2296
+ /* @__PURE__ */ jsx9(
2297
+ "span",
2298
+ {
2299
+ className: cn6(
2300
+ "absolute inset-x-0 top-0 h-0.5 opacity-60",
2301
+ role.accentBar
2302
+ )
2303
+ }
2304
+ ),
2305
+ /* @__PURE__ */ jsxs7(
2306
+ "button",
2307
+ {
2308
+ type: "button",
2309
+ disabled: !canDrillIn,
2310
+ onClick: () => setListOpen(true),
2311
+ title: canDrillIn ? `View the ${info.labelPlural.toLowerCase()} attached to ${container.label ?? "this"}` : void 0,
2312
+ className: cn6(
2313
+ "flex min-w-0 flex-1 items-center gap-3 px-3 py-2.5 text-left",
2314
+ canDrillIn ? "cursor-pointer" : "cursor-default"
2315
+ ),
2316
+ children: [
2317
+ /* @__PURE__ */ jsx9(
2318
+ "span",
2319
+ {
2320
+ className: cn6(
2321
+ "flex h-9 w-9 shrink-0 items-center justify-center rounded-lg",
2322
+ role.accentBg,
2323
+ role.accentText
2324
+ ),
2325
+ children: /* @__PURE__ */ jsx9(Icon, { className: "h-4 w-4" })
2326
+ }
2327
+ ),
2328
+ /* @__PURE__ */ jsxs7("span", { className: "min-w-0 flex-1", children: [
2329
+ /* @__PURE__ */ jsx9("span", { className: "block truncate text-sm font-medium text-foreground", children: info.labelPlural }),
2330
+ /* @__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: [
2331
+ /* @__PURE__ */ jsx9(SpinnerIcon, { className: "h-3 w-3 animate-spin" }),
2332
+ "Loading\u2026"
2333
+ ] }) : `${count} attached` })
2334
+ ] })
2335
+ ]
2336
+ }
2337
+ ),
2338
+ canAttach && /* @__PURE__ */ jsx9(
2339
+ "button",
2340
+ {
2341
+ type: "button",
2342
+ onClick: () => setOpen(true),
2343
+ "aria-label": `Attach ${info.labelPlural.toLowerCase()}`,
2344
+ title: `Attach ${info.labelPlural.toLowerCase()}`,
2345
+ 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",
2346
+ children: /* @__PURE__ */ jsx9(PlusIcon, { className: "h-4 w-4" })
2347
+ }
2348
+ )
2349
+ ]
2350
+ }
2351
+ ),
2352
+ listOpen && /* @__PURE__ */ jsx9(
2353
+ AttachedItemsSheet,
2354
+ {
2355
+ open: listOpen,
2356
+ onOpenChange: setListOpen,
2357
+ token,
2358
+ ...container.label !== void 0 ? { containerLabel: container.label } : {},
2359
+ container,
2360
+ links: linksFor(token),
2361
+ ...canAttach ? {
2362
+ onAdd: () => {
2363
+ setListOpen(false);
2364
+ setOpen(true);
2365
+ }
2366
+ } : {},
2367
+ onDetach: (resourceId) => detach(token, resourceId)
2368
+ }
2369
+ ),
2370
+ canAttach && /* @__PURE__ */ jsx9(
2371
+ AssociationPicker,
2372
+ {
2373
+ open,
2374
+ onOpenChange: setOpen,
2375
+ token,
2376
+ ...container.label !== void 0 ? { containerLabel: container.label } : {},
2377
+ orgId: container.orgId ?? null,
2378
+ attachedIds: attachedIdsFor(token),
2379
+ onAttach: (resourceId, title) => attach(token, resourceId, title),
2380
+ onDetach: (resourceId) => detach(token, resourceId)
2381
+ }
2382
+ )
2383
+ ] });
2384
+ }
2385
+
2386
+ // src/react/components/AssociationCardGrid.tsx
2387
+ import { cn as cn7 } from "@ai-matrx/design-system";
2388
+ import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
2389
+ function AssociationCardGrid({
2390
+ tokens,
2391
+ excludeTokens,
2392
+ className
2393
+ }) {
2394
+ const store = useAssociationsStore();
2395
+ const excluded = new Set(excludeTokens ?? []);
2396
+ const list = (tokens ?? store.registry.curatedTokens()).filter(
2397
+ (token) => !excluded.has(token)
2398
+ );
2399
+ return /* @__PURE__ */ jsx10("div", { className: cn7("space-y-5", className), children: CONTENT_ROLES.map((role) => {
2400
+ const inRole = list.filter(
2401
+ (token) => store.registry.getEntityInfo(token).contentRole === role.id
2402
+ );
2403
+ if (inRole.length === 0) return null;
2404
+ return /* @__PURE__ */ jsxs8("section", { children: [
2405
+ /* @__PURE__ */ jsxs8("div", { className: "mb-2.5 flex items-baseline gap-3 pl-1.5", children: [
2406
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2", children: [
2407
+ /* @__PURE__ */ jsx10(
2408
+ "span",
2409
+ {
2410
+ className: cn7("h-2.5 w-2.5 rounded-full", role.accentBar)
2411
+ }
2412
+ ),
2413
+ /* @__PURE__ */ jsx10("h3", { className: "text-sm font-semibold text-foreground", children: role.title })
2414
+ ] }),
2415
+ /* @__PURE__ */ jsx10("p", { className: "hidden text-xs text-muted-foreground sm:block", children: role.tagline })
2416
+ ] }),
2417
+ /* @__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)) })
2418
+ ] }, role.id);
2419
+ }) });
2420
+ }
2421
+
2422
+ // src/react/components/AssociationList.tsx
2423
+ import { useState as useState10 } from "react";
2424
+ import { cn as cn8, Skeleton } from "@ai-matrx/design-system";
2425
+ import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
2426
+ function useContainerLinksAdapter(container, tokens) {
2427
+ const store = useAssociationsStore();
2428
+ const links = useContainerLinks({
2429
+ containerType: container?.type ?? "organization",
2430
+ containerId: container?.id ?? null,
2431
+ orgId: container?.orgId ?? null
2432
+ });
2433
+ const rows = container ? (tokens ?? store.registry.curatedTokens()).flatMap(
2434
+ (token) => links.linksFor(token).map((l) => ({
2435
+ key: l.edgeId,
2436
+ token: l.token,
2437
+ resourceId: l.resourceId,
2438
+ label: l.label,
2439
+ role: l.role,
2440
+ removable: true
2441
+ }))
2442
+ ) : [];
2443
+ return {
2444
+ status: links.status,
2445
+ error: links.error,
2446
+ reload: links.reload,
2447
+ rows,
2448
+ attach: async (token, id, title) => links.attach(token, id, title),
2449
+ detach: async (token, id, role) => links.detach(token, id, role)
2450
+ };
2451
+ }
2452
+ function AssociationList(props) {
2453
+ const store = useAssociationsStore();
2454
+ const ctxEntity = usePrimaryEntity();
2455
+ const container = props.container ?? ctxEntity ?? null;
2456
+ const defaultAdapter = useContainerLinksAdapter(
2457
+ props.adapter ? null : container,
2458
+ props.tokens
2459
+ );
2460
+ const adapter = props.adapter ?? defaultAdapter;
2461
+ const variant = props.variant ?? "full";
2462
+ const tokenFilter = props.tokens ?? null;
2463
+ const [pickerToken, setPickerToken] = useState10(null);
2464
+ const [showUniversal, setShowUniversal] = useState10(false);
2465
+ const [removingKeys, setRemovingKeys] = useState10(/* @__PURE__ */ new Set());
2466
+ const rows = tokenFilter ? adapter.rows.filter((r) => tokenFilter.includes(r.token)) : adapter.rows;
2467
+ const visibleRows = rows.filter((r) => !removingKeys.has(r.key));
2468
+ const { titleFor } = useEntityTitles(
2469
+ visibleRows.map((r) => ({
2470
+ token: r.token,
2471
+ id: r.resourceId,
2472
+ label: r.label
2473
+ }))
2474
+ );
2475
+ const attachedKeys = new Set(
2476
+ rows.map((r) => attachedKey(r.token, r.resourceId))
2477
+ );
2478
+ const handleDetach = async (row) => {
2479
+ setRemovingKeys((prev) => new Set(prev).add(row.key));
2480
+ const info = store.registry.tryGetEntityInfo(row.token);
2481
+ const res = info ? await adapter.detach(info.token, row.resourceId, row.role ?? void 0) : { ok: false };
2482
+ setRemovingKeys((prev) => {
2483
+ const next = new Set(prev);
2484
+ next.delete(row.key);
2485
+ return next;
2486
+ });
2487
+ return res;
2488
+ };
2489
+ const openRow = (row) => {
2490
+ if (props.openEntity) {
2491
+ props.openEntity(row.token, row.resourceId);
2492
+ return;
2493
+ }
2494
+ const info = store.registry.tryGetEntityInfo(row.token);
2495
+ const href = info?.hrefFor?.(row.resourceId);
2496
+ if (href && typeof window !== "undefined") {
2497
+ window.open(href, "_blank", "noopener,noreferrer");
2498
+ }
2499
+ };
2500
+ const grouped = groupRows(store, visibleRows);
2501
+ const isLoading = adapter.status === "loading" || adapter.status === "idle";
2502
+ const attachableTokens = tokenFilter ?? store.registry.curatedTokens();
2503
+ return /* @__PURE__ */ jsxs9("div", { className: cn8("flex flex-col gap-2 text-foreground", props.className), children: [
2504
+ /* @__PURE__ */ jsxs9("div", { className: "flex items-center justify-between gap-2 px-1", children: [
2505
+ /* @__PURE__ */ jsxs9("h3", { className: "flex items-center gap-1.5 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: [
2506
+ /* @__PURE__ */ jsx11(Link2Icon, { className: "h-3.5 w-3.5" }),
2507
+ "Resources",
2508
+ visibleRows.length > 0 && /* @__PURE__ */ jsx11("span", { className: "rounded bg-muted px-1 text-[10px] font-medium text-muted-foreground", children: visibleRows.length })
2509
+ ] }),
2510
+ /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-1.5", children: [
2511
+ /* @__PURE__ */ jsxs9(
2512
+ "button",
2513
+ {
2514
+ type: "button",
2515
+ onClick: () => setShowUniversal((v) => !v),
2516
+ className: cn8(
2517
+ "inline-flex items-center gap-1 rounded-md border border-border px-1.5 py-0.5 text-[11px] transition-colors",
2518
+ showUniversal ? "bg-accent text-foreground" : "text-muted-foreground hover:bg-accent hover:text-foreground"
2519
+ ),
2520
+ children: [
2521
+ /* @__PURE__ */ jsx11(PlusIcon, { className: "h-3 w-3" }),
2522
+ "Add"
2523
+ ]
2524
+ }
2525
+ ),
2526
+ /* @__PURE__ */ jsx11(
2527
+ "button",
2528
+ {
2529
+ type: "button",
2530
+ onClick: () => void adapter.reload(),
2531
+ title: "Refresh",
2532
+ className: "text-muted-foreground/60 transition-colors hover:text-foreground",
2533
+ children: /* @__PURE__ */ jsx11(
2534
+ RefreshIcon,
2535
+ {
2536
+ className: cn8(
2537
+ "h-3 w-3",
2538
+ adapter.status === "loading" && "animate-spin"
2539
+ )
2540
+ }
2541
+ )
2542
+ }
2543
+ )
2544
+ ] })
2545
+ ] }),
2546
+ 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(
2547
+ UniversalAssociationPicker,
2548
+ {
2549
+ tokens: attachableTokens,
2550
+ orgId: container?.orgId ?? null,
2551
+ attachedKeys,
2552
+ onAttach: (token, id, title) => adapter.attach(token, id, title),
2553
+ onDetach: (token, id) => adapter.detach(token, id)
2554
+ }
2555
+ ) }),
2556
+ 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: [
2557
+ /* @__PURE__ */ jsx11("span", { className: "truncate text-[11px] text-destructive", children: adapter.error || "Failed to load resources" }),
2558
+ /* @__PURE__ */ jsx11(
2559
+ "button",
2560
+ {
2561
+ type: "button",
2562
+ onClick: () => void adapter.reload(),
2563
+ className: "text-[11px] font-medium text-destructive underline hover:no-underline",
2564
+ children: "Retry"
2565
+ }
2566
+ )
2567
+ ] }),
2568
+ adapter.status !== "error" && isLoading && visibleRows.length === 0 && /* @__PURE__ */ jsxs9("div", { className: "space-y-1.5 px-1", children: [
2569
+ /* @__PURE__ */ jsx11(Skeleton, { className: "h-3 w-20" }),
2570
+ /* @__PURE__ */ jsx11(Skeleton, { className: "h-7 w-full rounded-md" }),
2571
+ /* @__PURE__ */ jsx11(Skeleton, { className: "h-7 w-3/4 rounded-md" })
2572
+ ] }),
2573
+ 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." }),
2574
+ visibleRows.length > 0 && /* @__PURE__ */ jsx11("div", { className: "space-y-2.5", children: grouped.map(({ role, tokens: tokenGroups }) => {
2575
+ const roleMeta = getContentRoleMeta(role);
2576
+ return /* @__PURE__ */ jsxs9("div", { className: "space-y-1.5", children: [
2577
+ variant === "full" && /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-1.5 px-1", children: [
2578
+ /* @__PURE__ */ jsx11(
2579
+ "span",
2580
+ {
2581
+ className: cn8(
2582
+ "h-3 w-0.5 rounded-full",
2583
+ roleMeta.accentBar
2584
+ )
2585
+ }
2586
+ ),
2587
+ /* @__PURE__ */ jsx11(
2588
+ "p",
2589
+ {
2590
+ className: cn8(
2591
+ "text-[10px] font-semibold uppercase tracking-wider",
2592
+ roleMeta.accentText
2593
+ ),
2594
+ children: roleMeta.title
2595
+ }
2596
+ )
2597
+ ] }),
2598
+ tokenGroups.map(({ info, token, rows: tokenRows }) => {
2599
+ const TokenIcon = info?.Icon ?? null;
2600
+ return /* @__PURE__ */ jsxs9("div", { className: "space-y-0.5", children: [
2601
+ /* @__PURE__ */ jsxs9("div", { className: "flex items-center justify-between gap-1 px-1", children: [
2602
+ /* @__PURE__ */ jsxs9("p", { className: "flex items-center gap-1 text-[10px] font-medium text-muted-foreground/80", children: [
2603
+ TokenIcon ? /* @__PURE__ */ jsx11(TokenIcon, { className: "h-3 w-3" }) : null,
2604
+ info?.labelPlural ?? titleize(token),
2605
+ /* @__PURE__ */ jsx11("span", { className: "text-muted-foreground/50", children: tokenRows.length })
2606
+ ] }),
2607
+ /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-1", children: [
2608
+ props.renderSectionActions && info ? props.renderSectionActions(info.token) : null,
2609
+ info?.canListCandidates && /* @__PURE__ */ jsx11(
2610
+ "button",
2611
+ {
2612
+ type: "button",
2613
+ onClick: () => setPickerToken(info.token),
2614
+ title: `Add ${info.labelPlural}`,
2615
+ className: "rounded p-0.5 text-muted-foreground/60 transition-colors hover:bg-accent hover:text-foreground",
2616
+ children: /* @__PURE__ */ jsx11(PlusIcon, { className: "h-3 w-3" })
2617
+ }
2618
+ )
2619
+ ] })
2620
+ ] }),
2621
+ /* @__PURE__ */ jsx11("ul", { className: "space-y-0.5", children: tokenRows.map((row) => {
2622
+ const busy = removingKeys.has(row.key);
2623
+ const custom = props.renderRow?.(row, {
2624
+ title: titleFor({
2625
+ token: row.token,
2626
+ id: row.resourceId,
2627
+ label: row.label
2628
+ }),
2629
+ busy,
2630
+ onDetach: () => void handleDetach(row),
2631
+ onOpen: () => openRow(row)
2632
+ });
2633
+ if (custom != null) {
2634
+ return /* @__PURE__ */ jsx11("li", { children: custom }, row.key);
2635
+ }
2636
+ return /* @__PURE__ */ jsx11("li", { children: /* @__PURE__ */ jsxs9(
2637
+ "div",
2638
+ {
2639
+ className: cn8(
2640
+ "group flex items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors hover:bg-accent",
2641
+ busy && "opacity-50"
2642
+ ),
2643
+ children: [
2644
+ /* @__PURE__ */ jsx11(
2645
+ DoorRef,
2646
+ {
2647
+ token: row.token,
2648
+ id: row.resourceId,
2649
+ name: titleFor({
2650
+ token: row.token,
2651
+ id: row.resourceId,
2652
+ label: row.label
2653
+ }),
2654
+ showIcon: Boolean(TokenIcon),
2655
+ onOpen: props.openEntity ? () => openRow(row) : void 0,
2656
+ openInNewTab: !props.openEntity,
2657
+ className: "min-w-0 flex-1 text-foreground"
2658
+ }
2659
+ ),
2660
+ row.originRefs && row.originRefs.length > 0 ? /* @__PURE__ */ jsxs9("span", { className: "flex shrink-0 items-center gap-1 text-[10px] text-muted-foreground/60", children: [
2661
+ "via",
2662
+ row.originRefs.map((origin) => /* @__PURE__ */ jsx11(
2663
+ DoorRef,
2664
+ {
2665
+ token: origin.token,
2666
+ id: origin.id,
2667
+ name: origin.label,
2668
+ showIcon: false,
2669
+ openInNewTab: true,
2670
+ className: "max-w-24 text-[10px] text-muted-foreground underline-offset-2"
2671
+ },
2672
+ `${origin.token}:${origin.id}`
2673
+ ))
2674
+ ] }) : row.originNote ? /* @__PURE__ */ jsx11("span", { className: "shrink-0 text-[10px] text-muted-foreground/60", children: row.originNote }) : null,
2675
+ adapter.setPinned && info && row.removable && /* @__PURE__ */ jsx11(
2676
+ "button",
2677
+ {
2678
+ type: "button",
2679
+ onClick: () => void adapter.setPinned?.(
2680
+ info.token,
2681
+ row.resourceId,
2682
+ !(row.pinned ?? false)
2683
+ ),
2684
+ title: row.pinned ? "Unpin (stops staying in agent context)" : "Pin \u2014 always in agent context",
2685
+ "aria-label": row.pinned ? "Unpin" : "Pin",
2686
+ className: cn8(
2687
+ "shrink-0 rounded p-0.5 transition-colors hover:!text-foreground",
2688
+ row.pinned ? "text-primary" : "text-muted-foreground/0 group-hover:text-muted-foreground"
2689
+ ),
2690
+ children: /* @__PURE__ */ jsx11(
2691
+ PinIcon,
2692
+ {
2693
+ className: cn8(
2694
+ "h-3 w-3",
2695
+ row.pinned && "fill-primary/20"
2696
+ )
2697
+ }
2698
+ )
2699
+ }
2700
+ ),
2701
+ row.removable && /* @__PURE__ */ jsx11(
2702
+ "button",
2703
+ {
2704
+ type: "button",
2705
+ disabled: busy,
2706
+ onClick: () => void handleDetach(row),
2707
+ title: "Detach",
2708
+ "aria-label": "Detach",
2709
+ className: "shrink-0 rounded p-0.5 text-muted-foreground/0 transition-colors group-hover:text-muted-foreground hover:!text-destructive disabled:opacity-50",
2710
+ children: busy ? /* @__PURE__ */ jsx11(SpinnerIcon, { className: "h-3 w-3 animate-spin" }) : /* @__PURE__ */ jsx11(XIcon, { className: "h-3 w-3" })
2711
+ }
2712
+ )
2713
+ ]
2714
+ }
2715
+ ) }, row.key);
2716
+ }) })
2717
+ ] }, token);
2718
+ })
2719
+ ] }, role);
2720
+ }) }),
2721
+ pickerToken && /* @__PURE__ */ jsx11(
2722
+ AssociationPicker,
2723
+ {
2724
+ open: true,
2725
+ onOpenChange: (open) => {
2726
+ if (!open) setPickerToken(null);
2727
+ },
2728
+ token: pickerToken,
2729
+ ...container?.label !== void 0 ? { containerLabel: container.label } : {},
2730
+ orgId: container?.orgId ?? null,
2731
+ attachedIds: new Set(
2732
+ rows.filter((r) => r.token === pickerToken).map((r) => r.resourceId)
2733
+ ),
2734
+ onAttach: (id, title) => adapter.attach(pickerToken, id, title),
2735
+ onDetach: (id) => adapter.detach(pickerToken, id)
2736
+ }
2737
+ )
2738
+ ] });
2739
+ }
2740
+ function groupRows(store, rows) {
2741
+ const byToken = /* @__PURE__ */ new Map();
2742
+ for (const r of rows) {
2743
+ const list = byToken.get(r.token) ?? [];
2744
+ list.push(r);
2745
+ byToken.set(r.token, list);
2746
+ }
2747
+ const roleBuckets = /* @__PURE__ */ new Map();
2748
+ for (const [token, tokenRows] of byToken) {
2749
+ const info = store.registry.tryGetEntityInfo(token);
2750
+ const role = info?.contentRole ?? "destination";
2751
+ tokenRows.sort(
2752
+ (a, b) => Number(b.pinned ?? false) - Number(a.pinned ?? false)
2753
+ );
2754
+ const groups = roleBuckets.get(role) ?? [];
2755
+ groups.push({ token, info, rows: tokenRows });
2756
+ roleBuckets.set(role, groups);
2757
+ }
2758
+ const out = [];
2759
+ for (const meta of CONTENT_ROLES) {
2760
+ const tokens = roleBuckets.get(meta.id);
2761
+ if (tokens && tokens.length > 0) {
2762
+ tokens.sort(
2763
+ (a, b) => (a.info?.labelPlural ?? a.token).localeCompare(
2764
+ b.info?.labelPlural ?? b.token
2765
+ )
2766
+ );
2767
+ out.push({ role: meta.id, tokens });
2768
+ }
2769
+ }
2770
+ return out;
2771
+ }
2772
+ function titleize(token) {
2773
+ return token.split("_").map((w) => w ? w[0].toUpperCase() + w.slice(1) : w).join(" ");
2774
+ }
2775
+
2776
+ // src/react/components/AssociationCaptureToolbar.tsx
2777
+ import { useRef as useRef6, useState as useState11 } from "react";
2778
+ import { Button, cn as cn9, Input as Input3 } from "@ai-matrx/design-system";
2779
+ import { Fragment as Fragment4, jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
2780
+ function AssociationCaptureToolbar({
2781
+ attach,
2782
+ uploadFolderPath,
2783
+ uploadVisibility = "personal",
2784
+ uploadLocationLabel,
2785
+ attachRetryLabel = '"Add file"',
2786
+ filePicker,
2787
+ showActions,
2788
+ showToolbar = true,
2789
+ extraActions,
2790
+ openCreatedDocument = true,
2791
+ className,
2792
+ children
2793
+ }) {
2794
+ const store = useAssociationsStore();
2795
+ const { capture } = useAssociationsUiPorts();
2796
+ const notifier = useNotifier();
2797
+ const fileInputRef = useRef6(null);
2798
+ const [isUploading, setIsUploading] = useState11(false);
2799
+ const [isDragOver, setIsDragOver] = useState11(false);
2800
+ const [isPicking, setIsPicking] = useState11(false);
2801
+ const [namingDoc, setNamingDoc] = useState11(false);
2802
+ const [docName, setDocName] = useState11("");
2803
+ const [creatingDoc, setCreatingDoc] = useState11(false);
2804
+ const actions = {
2805
+ upload: (showActions?.upload ?? true) && Boolean(capture?.requestUpload),
2806
+ addFile: (showActions?.addFile ?? true) && Boolean(capture?.openFilePicker),
2807
+ newDocument: (showActions?.newDocument ?? true) && Boolean(capture?.createDataTable)
2808
+ };
2809
+ const canDrop = Boolean(capture?.requestUpload);
2810
+ const uploadAndAttach = async (files) => {
2811
+ const requestUpload = capture?.requestUpload;
2812
+ if (files.length === 0 || !requestUpload) return;
2813
+ setIsUploading(true);
2814
+ const watchdog = setTimeout(() => {
2815
+ store.errorSink({
2816
+ code: "capture_upload_unresponsive",
2817
+ message: "[AssociationCaptureToolbar] upload pipeline unresponsive >60s",
2818
+ context: { files: files.map((f) => f.name) }
2819
+ });
2820
+ notifier.error(
2821
+ "The upload isn't responding \u2014 it may be stalled. Check your connection and retry."
2822
+ );
2823
+ }, 6e4);
2824
+ try {
2825
+ const result = await requestUpload({
2826
+ files,
2827
+ folderPath: uploadFolderPath,
2828
+ visibility: uploadVisibility
2829
+ });
2830
+ if (result.cancelled) {
2831
+ notifier.success("Upload cancelled \u2014 nothing was attached");
2832
+ return;
2833
+ }
2834
+ const aliasedIds = result.aliased.map((a) => a.existingFileId);
2835
+ const ids = [...result.uploaded, ...aliasedIds];
2836
+ let attached = 0;
2837
+ const unattached = [];
2838
+ let firstAttachError = null;
2839
+ for (const id of ids) {
2840
+ const res = await attach("file", id);
2841
+ if (res.ok) {
2842
+ attached += 1;
2843
+ } else {
2844
+ unattached.push(id);
2845
+ if (!firstAttachError && res.error) firstAttachError = res.error;
2846
+ }
2847
+ }
2848
+ if (attached > 0) {
2849
+ notifier.success(
2850
+ attached === 1 ? "Attached 1 file" : `Attached ${attached} files`
2851
+ );
2852
+ }
2853
+ if (unattached.length > 0) {
2854
+ notifier.error(
2855
+ `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})` : "")
2856
+ );
2857
+ }
2858
+ if (result.failed.length > 0) {
2859
+ const first = result.failed[0];
2860
+ notifier.error(
2861
+ result.failed.length === 1 && first ? `Failed to upload ${first.name}: ${first.error}` : `Failed to upload ${result.failed.length} files`
2862
+ );
2863
+ }
2864
+ if (ids.length === 0 && result.failed.length === 0) {
2865
+ notifier.error("Nothing was uploaded or attached");
2866
+ }
2867
+ } catch (err) {
2868
+ store.errorSink({
2869
+ code: "capture_upload_failed",
2870
+ message: "[AssociationCaptureToolbar] upload flow failed",
2871
+ context: { detail: err }
2872
+ });
2873
+ notifier.error(
2874
+ err instanceof Error && err.message ? `Upload failed: ${err.message}` : "Upload failed unexpectedly"
2875
+ );
2876
+ } finally {
2877
+ clearTimeout(watchdog);
2878
+ setIsUploading(false);
2879
+ }
2880
+ };
2881
+ const handleFilesSelected = (e) => {
2882
+ const files = e.target.files ? Array.from(e.target.files) : [];
2883
+ e.target.value = "";
2884
+ void uploadAndAttach(files);
2885
+ };
2886
+ const handleDrop = (e) => {
2887
+ if (!canDrop) return;
2888
+ e.preventDefault();
2889
+ e.stopPropagation();
2890
+ setIsDragOver(false);
2891
+ const files = Array.from(e.dataTransfer?.files ?? []);
2892
+ if (files.length === 0) return;
2893
+ void uploadAndAttach(files);
2894
+ };
2895
+ const handlePickExisting = async () => {
2896
+ const openFilePicker = capture?.openFilePicker;
2897
+ if (isPicking || !openFilePicker) return;
2898
+ setIsPicking(true);
2899
+ try {
2900
+ const ids = await openFilePicker({
2901
+ multi: true,
2902
+ title: filePicker?.title ?? "Attach files",
2903
+ description: filePicker?.description ?? "Pick existing files from your cloud storage."
2904
+ });
2905
+ if (!ids || ids.length === 0) return;
2906
+ let attached = 0;
2907
+ let firstAttachError = null;
2908
+ for (const id of ids) {
2909
+ const res = await attach("file", id);
2910
+ if (res.ok) attached += 1;
2911
+ else if (!firstAttachError && res.error) firstAttachError = res.error;
2912
+ }
2913
+ if (attached > 0) {
2914
+ notifier.success(
2915
+ attached === 1 ? "Attached 1 file" : `Attached ${attached} files`
2916
+ );
2917
+ }
2918
+ if (attached < ids.length && firstAttachError) {
2919
+ notifier.error(`Couldn't attach: ${firstAttachError}`);
2920
+ }
2921
+ } catch (err) {
2922
+ store.errorSink({
2923
+ code: "capture_pick_failed",
2924
+ message: "[AssociationCaptureToolbar] attach-existing flow failed",
2925
+ context: { detail: err }
2926
+ });
2927
+ notifier.error("Couldn't attach the selected files");
2928
+ } finally {
2929
+ setIsPicking(false);
2930
+ }
2931
+ };
2932
+ const handleCreateDoc = async () => {
2933
+ const createDataTable = capture?.createDataTable;
2934
+ const name = docName.trim();
2935
+ if (!name || creatingDoc || !createDataTable) return;
2936
+ setCreatingDoc(true);
2937
+ try {
2938
+ const doc = await createDataTable({ name });
2939
+ if (!doc) {
2940
+ notifier.error("Couldn't create the document");
2941
+ return;
2942
+ }
2943
+ const res = await attach("udt_document", doc.id, { label: name });
2944
+ setNamingDoc(false);
2945
+ setDocName("");
2946
+ if (res.ok) {
2947
+ notifier.success("Document created");
2948
+ if (openCreatedDocument && typeof window !== "undefined") {
2949
+ const href = store.registry.tryGetEntityInfo("udt_document")?.hrefFor?.(doc.id);
2950
+ if (href) window.open(href, "_blank", "noopener,noreferrer");
2951
+ }
2952
+ } else {
2953
+ notifier.error(
2954
+ `Created "${name}" but couldn't attach it here \u2014 it's saved in your documents; retry from its library`
2955
+ );
2956
+ }
2957
+ } catch (err) {
2958
+ store.errorSink({
2959
+ code: "capture_create_document_failed",
2960
+ message: "[AssociationCaptureToolbar] create-document flow failed",
2961
+ context: { detail: err }
2962
+ });
2963
+ notifier.error("Couldn't create the document");
2964
+ } finally {
2965
+ setCreatingDoc(false);
2966
+ }
2967
+ };
2968
+ const anyBuiltIn = actions.upload || actions.addFile || actions.newDocument;
2969
+ return /* @__PURE__ */ jsxs10(
2970
+ "div",
2971
+ {
2972
+ className: cn9(
2973
+ "relative flex h-full min-h-0 flex-col",
2974
+ isDragOver && "ring-2 ring-inset ring-primary/50",
2975
+ className
2976
+ ),
2977
+ onDragOver: (e) => {
2978
+ if (!canDrop) return;
2979
+ if (!e.dataTransfer?.types.includes("Files")) return;
2980
+ e.preventDefault();
2981
+ e.stopPropagation();
2982
+ setIsDragOver(true);
2983
+ },
2984
+ onDragLeave: (e) => {
2985
+ if (e.currentTarget.contains(e.relatedTarget)) return;
2986
+ setIsDragOver(false);
2987
+ },
2988
+ onDrop: handleDrop,
2989
+ children: [
2990
+ 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: [
2991
+ /* @__PURE__ */ jsx12(UploadIcon, { className: "size-3.5 text-primary" }),
2992
+ "Drop to upload & attach"
2993
+ ] }) }) : null,
2994
+ /* @__PURE__ */ jsx12(
2995
+ "input",
2996
+ {
2997
+ ref: fileInputRef,
2998
+ type: "file",
2999
+ multiple: true,
3000
+ className: "sr-only",
3001
+ onChange: handleFilesSelected
3002
+ }
3003
+ ),
3004
+ 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: [
3005
+ /* @__PURE__ */ jsx12(
3006
+ Input3,
3007
+ {
3008
+ autoFocus: true,
3009
+ value: docName,
3010
+ disabled: creatingDoc,
3011
+ placeholder: "Document name\u2026",
3012
+ onChange: (e) => setDocName(e.target.value),
3013
+ onKeyDown: (e) => {
3014
+ if (e.key === "Enter") void handleCreateDoc();
3015
+ if (e.key === "Escape") {
3016
+ setNamingDoc(false);
3017
+ setDocName("");
3018
+ }
3019
+ },
3020
+ className: "h-7 flex-1 text-base md:text-xs",
3021
+ style: { fontSize: 16 }
3022
+ }
3023
+ ),
3024
+ /* @__PURE__ */ jsx12(
3025
+ "button",
3026
+ {
3027
+ type: "button",
3028
+ disabled: creatingDoc || !docName.trim(),
3029
+ onClick: () => void handleCreateDoc(),
3030
+ className: "flex h-7 items-center rounded-md bg-primary px-2 text-[11px] font-medium text-primary-foreground disabled:opacity-50",
3031
+ children: creatingDoc ? /* @__PURE__ */ jsx12(SpinnerIcon, { className: "size-3.5 animate-spin" }) : "Create"
3032
+ }
3033
+ ),
3034
+ /* @__PURE__ */ jsx12(
3035
+ "button",
3036
+ {
3037
+ type: "button",
3038
+ disabled: creatingDoc,
3039
+ onClick: () => {
3040
+ setNamingDoc(false);
3041
+ setDocName("");
3042
+ },
3043
+ className: "flex h-7 items-center rounded-md border border-border px-2 text-[11px] text-muted-foreground hover:text-foreground disabled:opacity-50",
3044
+ children: "Cancel"
3045
+ }
3046
+ )
3047
+ ] }) : /* @__PURE__ */ jsxs10(Fragment4, { children: [
3048
+ actions.upload ? /* @__PURE__ */ jsx12(
3049
+ CaptureToolbarAction,
3050
+ {
3051
+ icon: isUploading ? SpinnerIcon : UploadIcon,
3052
+ spinning: isUploading,
3053
+ label: "Upload",
3054
+ onClick: () => !isUploading && fileInputRef.current?.click(),
3055
+ disabled: isPicking || isUploading
3056
+ }
3057
+ ) : null,
3058
+ actions.addFile ? /* @__PURE__ */ jsx12(
3059
+ CaptureToolbarAction,
3060
+ {
3061
+ icon: isPicking ? SpinnerIcon : FolderOpenIcon,
3062
+ spinning: isPicking,
3063
+ label: "Add file",
3064
+ onClick: () => void handlePickExisting(),
3065
+ disabled: isPicking || isUploading
3066
+ }
3067
+ ) : null,
3068
+ (actions.upload || actions.addFile) && actions.newDocument ? /* @__PURE__ */ jsx12("span", { className: "mx-1 h-4 w-px bg-border" }) : null,
3069
+ actions.newDocument ? /* @__PURE__ */ jsx12(
3070
+ CaptureToolbarAction,
3071
+ {
3072
+ icon: creatingDoc ? SpinnerIcon : PlusIcon,
3073
+ spinning: creatingDoc,
3074
+ label: "New document",
3075
+ onClick: () => setNamingDoc(true),
3076
+ disabled: creatingDoc
3077
+ }
3078
+ ) : null,
3079
+ extraActions
3080
+ ] }) }) : null,
3081
+ children
3082
+ ]
3083
+ }
3084
+ );
3085
+ }
3086
+ function CaptureToolbarAction({
3087
+ icon: Icon,
3088
+ label,
3089
+ onClick,
3090
+ disabled,
3091
+ spinning
3092
+ }) {
3093
+ return /* @__PURE__ */ jsxs10(
3094
+ Button,
3095
+ {
3096
+ type: "button",
3097
+ size: "sm",
3098
+ variant: "ghost",
3099
+ onClick,
3100
+ disabled,
3101
+ className: "h-7 gap-1 px-2 text-[11px] text-muted-foreground hover:text-foreground",
3102
+ children: [
3103
+ /* @__PURE__ */ jsx12(Icon, { className: cn9("size-3.5", spinning && "animate-spin") }),
3104
+ label
3105
+ ]
3106
+ }
3107
+ );
3108
+ }
3109
+ export {
3110
+ AssociationCandidateBody,
3111
+ AssociationCaptureToolbar,
3112
+ AssociationCard,
3113
+ AssociationCardGrid,
3114
+ AssociationList,
3115
+ AssociationPicker,
3116
+ AssociationWindow,
3117
+ AssociationsProvider,
3118
+ AttachedItemsSheet,
3119
+ CONTENT_ROLES,
3120
+ CaptureToolbarAction,
3121
+ DefaultEntityIcon,
3122
+ DoorRef,
3123
+ PrimaryEntityProvider,
3124
+ UniversalAssociationPicker,
3125
+ UnresolvedRef,
3126
+ attachedKey,
3127
+ getContentRoleMeta,
3128
+ useAssociationCandidates,
3129
+ useAssociationEntitySelectAdapter,
3130
+ useAssociations,
3131
+ useAssociationsStore,
3132
+ useAssociationsUiPorts,
3133
+ useCategories,
3134
+ useContainerLinks,
3135
+ useContainerLinksAdapter,
3136
+ useEntityRelationships,
3137
+ useEntityTitles,
3138
+ useNotifier,
3139
+ usePrimaryEntity,
3140
+ useUniversalEntitySearch
3141
+ };
3142
+ //# sourceMappingURL=index.js.map