@ai-matrx/associations 0.2.0 → 0.4.0

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