@ai-matrx/associations 0.2.0 → 0.3.0

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