collavre 0.24.0 → 0.25.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.
- checksums.yaml +4 -4
- data/README.md +0 -1
- data/app/assets/stylesheets/collavre/comments_popup.css +165 -99
- data/app/assets/stylesheets/collavre/creatives.css +346 -37
- data/app/assets/stylesheets/collavre/desktop_setup.css +246 -0
- data/app/assets/stylesheets/collavre/gnb.css +64 -3
- data/app/assets/stylesheets/collavre/landing.css +108 -3
- data/app/assets/stylesheets/collavre/mention_menu.css +32 -0
- data/app/assets/stylesheets/collavre/popup.css +14 -0
- data/app/assets/stylesheets/collavre/print.css +0 -1
- data/app/assets/stylesheets/collavre/search_popup.css +1 -1
- data/app/assets/stylesheets/collavre/secret_fields.css +3 -0
- data/app/assets/stylesheets/collavre/workspace.css +307 -0
- data/app/channels/collavre/agent_channel.rb +14 -21
- data/app/channels/collavre/comments_presence_channel.rb +181 -21
- data/app/channels/collavre/inbox_badge_channel.rb +7 -1
- data/app/components/collavre/inbox/badge_component.rb +6 -13
- data/app/components/collavre/progress_filter_component.html.erb +2 -2
- data/app/components/collavre/progress_filter_component.rb +20 -0
- data/app/controllers/collavre/admin/settings_controller.rb +4 -1
- data/app/controllers/collavre/agent_connections_controller.rb +165 -0
- data/app/controllers/collavre/agent_gateways_controller.rb +132 -0
- data/app/controllers/collavre/agent_provisioning_controller.rb +79 -0
- data/app/controllers/collavre/api/v1/agents_controller.rb +45 -97
- data/app/controllers/collavre/application_controller.rb +13 -2
- data/app/controllers/collavre/comment_read_pointers_controller.rb +69 -40
- data/app/controllers/collavre/comments_controller.rb +45 -131
- data/app/controllers/collavre/concerns/shareable.rb +9 -3
- data/app/controllers/collavre/creatives_controller.rb +144 -11
- data/app/controllers/collavre/desktop_gateway_registrations_controller.rb +173 -0
- data/app/controllers/collavre/desktop_setup_controller.rb +119 -0
- data/app/controllers/collavre/desktop_sidecar_health_controller.rb +35 -0
- data/app/controllers/collavre/features_controller.rb +57 -0
- data/app/controllers/collavre/invites_controller.rb +12 -1
- data/app/controllers/collavre/llm_models_controller.rb +8 -0
- data/app/controllers/collavre/notices_controller.rb +20 -0
- data/app/controllers/collavre/tasks_controller.rb +40 -6
- data/app/controllers/collavre/topics_controller.rb +130 -53
- data/app/controllers/collavre/user_creative_preferences_controller.rb +214 -30
- data/app/controllers/concerns/collavre/comments/batch_operations.rb +15 -3
- data/app/controllers/concerns/collavre/comments/conversion.rb +1 -1
- data/app/controllers/concerns/collavre/users_controller/ai_user_management.rb +118 -8
- data/app/controllers/concerns/collavre/users_controller/profile_and_settings.rb +1 -15
- data/app/controllers/concerns/collavre/users_controller/registration.rb +2 -2
- data/app/errors/collavre/turn_deadline_error.rb +20 -0
- data/app/helpers/collavre/application_helper.rb +38 -0
- data/app/helpers/collavre/creatives_helper.rb +56 -22
- data/app/helpers/collavre/navigation_helper.rb +12 -0
- data/app/javascript/collavre.js +1 -2
- data/app/javascript/components/InlineLexicalEditor.jsx +30 -21
- data/app/javascript/components/__tests__/creative_tree_row_parent_id.test.js +32 -2
- data/app/javascript/components/__tests__/creative_tree_row_progress_toggle.test.js +185 -0
- data/app/javascript/components/creative_tree_row.js +62 -22
- data/app/javascript/controllers/__tests__/agent_connection_controller.test.js +195 -0
- data/app/javascript/controllers/__tests__/desktop_proxy_setup_controller.test.js +96 -0
- data/app/javascript/controllers/__tests__/index.test.js +42 -0
- data/app/javascript/controllers/__tests__/last_visited_creative_controller.test.js +103 -0
- data/app/javascript/controllers/__tests__/link_creative_controller.test.js +253 -2
- data/app/javascript/controllers/__tests__/progress_filter_controller.test.js +134 -0
- data/app/javascript/controllers/__tests__/search_popup_controller.test.js +310 -0
- data/app/javascript/controllers/__tests__/topic_list_controller.test.js +111 -0
- data/app/javascript/controllers/__tests__/workspace_tree_controller.test.js +1011 -0
- data/app/javascript/controllers/agent_connection_controller.js +357 -0
- data/app/javascript/controllers/agent_vendor_controller.js +23 -0
- data/app/javascript/controllers/comment_controller.js +3 -1
- data/app/javascript/controllers/comments/__tests__/feature_cards_controller.test.js +223 -0
- data/app/javascript/controllers/comments/__tests__/form_controller_default_placeholder.test.js +83 -0
- data/app/javascript/controllers/comments/__tests__/form_controller_draft.test.js +2069 -0
- data/app/javascript/controllers/comments/__tests__/form_controller_ime_send.test.js +324 -0
- data/app/javascript/controllers/comments/__tests__/form_controller_review.test.js +28 -2
- data/app/javascript/controllers/comments/__tests__/form_controller_search_submit.test.js +106 -0
- data/app/javascript/controllers/comments/__tests__/form_controller_stashed_draft.test.js +134 -0
- data/app/javascript/controllers/comments/__tests__/form_controller_stashed_draft_creative_switch.test.js +166 -0
- data/app/javascript/controllers/comments/__tests__/form_controller_stashed_draft_failure.test.js +183 -0
- data/app/javascript/controllers/comments/__tests__/list_controller_deep_link_topic.test.js +467 -0
- data/app/javascript/controllers/comments/__tests__/list_controller_docked.test.js +115 -0
- data/app/javascript/controllers/comments/__tests__/list_controller_prev_message.test.js +245 -0
- data/app/javascript/controllers/comments/__tests__/list_controller_read_pointer.test.js +276 -0
- data/app/javascript/controllers/comments/__tests__/list_controller_stream_archived.test.js +221 -0
- data/app/javascript/controllers/comments/__tests__/list_controller_stream_search.test.js +104 -0
- data/app/javascript/controllers/comments/__tests__/placeholder_controller.test.js +79 -0
- data/app/javascript/controllers/comments/__tests__/popup_controller.test.js +754 -1
- data/app/javascript/controllers/comments/__tests__/presence_controller.test.js +717 -15
- data/app/javascript/controllers/comments/__tests__/prev_message_navigator.test.js +129 -0
- data/app/javascript/controllers/comments/__tests__/topics_controller_action_errors.test.js +184 -0
- data/app/javascript/controllers/comments/__tests__/topics_controller_archived.test.js +879 -0
- data/app/javascript/controllers/comments/__tests__/topics_controller_creation_container.test.js +154 -0
- data/app/javascript/controllers/comments/__tests__/topics_controller_creation_scroll.test.js +92 -0
- data/app/javascript/controllers/comments/__tests__/topics_controller_delete.test.js +130 -0
- data/app/javascript/controllers/comments/__tests__/topics_controller_docked.test.js +32 -0
- data/app/javascript/controllers/comments/__tests__/topics_controller_last_topic_echo.test.js +1938 -0
- data/app/javascript/controllers/comments/__tests__/topics_controller_primary_agent.test.js +256 -0
- data/app/javascript/controllers/comments/__tests__/topics_controller_scroll.test.js +182 -0
- data/app/javascript/controllers/comments/__tests__/topics_controller_selection_race.test.js +981 -0
- data/app/javascript/controllers/comments/__tests__/topics_controller_topic_list.test.js +313 -0
- data/app/javascript/controllers/comments/feature_cards_controller.js +83 -0
- data/app/javascript/controllers/comments/form_controller.js +795 -14
- data/app/javascript/controllers/comments/list_controller.js +338 -52
- data/app/javascript/controllers/comments/placeholder_controller.js +30 -0
- data/app/javascript/controllers/comments/popup_controller.js +425 -83
- data/app/javascript/controllers/comments/presence_controller.js +317 -55
- data/app/javascript/controllers/comments/prev_message_navigator.js +94 -0
- data/app/javascript/controllers/comments/topics_controller.js +1772 -100
- data/app/javascript/controllers/common_popup_controller.js +2 -2
- data/app/javascript/controllers/creatives/__tests__/select_mode_controller.test.js +139 -0
- data/app/javascript/controllers/creatives/__tests__/tree_controller.test.js +605 -0
- data/app/javascript/controllers/creatives/__tests__/write_access_request_controller.test.js +125 -0
- data/app/javascript/controllers/creatives/select_mode_controller.js +7 -2
- data/app/javascript/controllers/creatives/tree_controller.js +140 -31
- data/app/javascript/controllers/creatives/write_access_request_controller.js +58 -0
- data/app/javascript/controllers/desktop_proxy_setup_controller.js +51 -0
- data/app/javascript/controllers/gateway_check_controller.js +36 -0
- data/app/javascript/controllers/index.js +25 -1
- data/app/javascript/controllers/last_visited_creative_controller.js +58 -0
- data/app/javascript/controllers/link_creative_controller.js +89 -18
- data/app/javascript/controllers/progress_filter_controller.js +12 -22
- data/app/javascript/controllers/search_popup_controller.js +64 -46
- data/app/javascript/controllers/share_user_search_controller.js +2 -2
- data/app/javascript/controllers/topic_list_controller.js +44 -7
- data/app/javascript/controllers/workspace_tree_controller.js +662 -0
- data/app/javascript/creatives/__tests__/tree_renderer.test.js +80 -0
- data/app/javascript/creatives/drag_drop/__tests__/drop_completion_empty_state.test.js +98 -0
- data/app/javascript/creatives/drag_drop/__tests__/topic_move_released_agent.test.js +125 -0
- data/app/javascript/creatives/drag_drop/event_handlers.js +12 -0
- data/app/javascript/creatives/tree_renderer.js +65 -22
- data/app/javascript/lib/__tests__/chat_drafts.test.js +1458 -0
- data/app/javascript/lib/__tests__/common_popup_bounds.test.js +8 -3
- data/app/javascript/lib/__tests__/common_popup_placement.test.js +374 -0
- data/app/javascript/lib/__tests__/common_popup_reshow.test.js +131 -0
- data/app/javascript/lib/__tests__/creative_link_prefetch.test.js +46 -0
- data/app/javascript/lib/__tests__/last_visited_creative.test.js +87 -0
- data/app/javascript/lib/__tests__/skill_html_entities.test.js +23 -0
- data/app/javascript/lib/__tests__/turbo_stream_actions.test.js +190 -0
- data/app/javascript/lib/api/__tests__/creatives.test.js +68 -0
- data/app/javascript/lib/api/__tests__/csrf_fetch.test.js +27 -0
- data/app/javascript/lib/api/__tests__/topics_save_last_topic.test.js +112 -0
- data/app/javascript/lib/api/creatives.js +32 -13
- data/app/javascript/lib/api/csrf_fetch.js +6 -4
- data/app/javascript/lib/api/topics.js +63 -5
- data/app/javascript/lib/chat_drafts.js +786 -0
- data/app/javascript/lib/common_popup.js +164 -25
- data/app/javascript/lib/creative_link_prefetch.js +27 -0
- data/app/javascript/lib/last_visited_creative.js +97 -0
- data/app/javascript/lib/lexical/__tests__/creative_link_node.test.js +162 -0
- data/app/javascript/lib/lexical/__tests__/creative_link_trigger.test.js +133 -0
- data/app/javascript/lib/lexical/__tests__/link_toolbar.test.js +62 -0
- data/app/javascript/lib/lexical/creative_link_node.js +101 -0
- data/app/javascript/lib/lexical/creative_link_trigger.js +98 -0
- data/app/javascript/lib/lexical/link_toolbar.js +23 -0
- data/app/javascript/lib/turbo_stream_actions.js +23 -16
- data/app/javascript/lib/utils/__tests__/filter_navigation.test.js +210 -0
- data/app/javascript/lib/utils/__tests__/markdown.test.js +44 -0
- data/app/javascript/lib/utils/filter_navigation.js +100 -0
- data/app/javascript/modules/__tests__/command_menu_creative_picker.test.js +62 -0
- data/app/javascript/modules/__tests__/command_menu_typeahead.test.js +267 -0
- data/app/javascript/modules/__tests__/creative_link_picker.test.js +85 -0
- data/app/javascript/modules/__tests__/creative_row_editor_archive.test.js +391 -0
- data/app/javascript/modules/__tests__/creative_row_editor_delegated_clicks.test.js +158 -0
- data/app/javascript/modules/__tests__/creative_row_editor_empty_state_recovery.test.js +519 -0
- data/app/javascript/modules/__tests__/creative_row_editor_empty_state_removal.test.js +346 -0
- data/app/javascript/modules/__tests__/creative_row_editor_session.test.js +144 -0
- data/app/javascript/modules/__tests__/creative_tree_dom.test.js +30 -0
- data/app/javascript/modules/__tests__/creative_tree_empty_state.test.js +186 -0
- data/app/javascript/modules/__tests__/dom_listener_registry.test.js +67 -0
- data/app/javascript/modules/__tests__/support/inline_editor_dom.js +159 -0
- data/app/javascript/modules/command_args_form.js +0 -12
- data/app/javascript/modules/command_menu.js +80 -38
- data/app/javascript/modules/creative_link_picker.js +56 -0
- data/app/javascript/modules/creative_row_editor.js +446 -243
- data/app/javascript/modules/creative_row_editor_delegated_clicks.js +87 -0
- data/app/javascript/modules/creative_tree_dom.js +4 -0
- data/app/javascript/modules/creative_tree_empty_state.js +80 -0
- data/app/javascript/modules/dom_listener_registry.js +23 -0
- data/app/javascript/modules/mention_menu.js +2 -3
- data/app/javascript/utils/__tests__/caret_anchor.test.js +75 -0
- data/app/javascript/utils/__tests__/chevron_icons.test.js +15 -0
- data/app/javascript/utils/caret_position.js +10 -0
- data/app/javascript/utils/chevron_icons.js +7 -0
- data/app/jobs/collavre/ai_agent_job.rb +262 -72
- data/app/jobs/collavre/cancel_offline_delegated_tasks_job.rb +11 -36
- data/app/jobs/collavre/comment_badges_broadcast_job.rb +28 -0
- data/app/jobs/collavre/comment_link_preview_job.rb +33 -0
- data/app/jobs/collavre/comment_notification_job.rb +14 -0
- data/app/jobs/collavre/comment_push_delivery_sweep_job.rb +17 -0
- data/app/jobs/collavre/compress_job.rb +33 -22
- data/app/jobs/collavre/creative_broadcast_job.rb +161 -48
- data/app/jobs/collavre/cron_action_job.rb +36 -18
- data/app/jobs/collavre/drop_trigger_job.rb +23 -9
- data/app/jobs/collavre/merge_comments_job.rb +33 -16
- data/app/jobs/collavre/push_notification_job.rb +62 -18
- data/app/jobs/collavre/restore_dropped_dispatches_job.rb +17 -0
- data/app/jobs/collavre/stuck_detector_job.rb +1 -1
- data/app/jobs/collavre/touch_creative_subtree_job.rb +14 -0
- data/app/models/collavre/agent_gateway.rb +200 -0
- data/app/models/collavre/agent_subscription.rb +4 -4
- data/app/models/collavre/agent_workspace.rb +210 -0
- data/app/models/collavre/comment/broadcastable.rb +22 -52
- data/app/models/collavre/comment/notifiable.rb +154 -77
- data/app/models/collavre/comment/topic_membership.rb +41 -0
- data/app/models/collavre/comment.rb +336 -31
- data/app/models/collavre/comment_notification_delivery.rb +61 -0
- data/app/models/collavre/comment_presence_store/cache_batches.rb +64 -0
- data/app/models/collavre/comment_presence_store.rb +272 -12
- data/app/models/collavre/comment_read_pointer.rb +9 -15
- data/app/models/collavre/creative/describable.rb +8 -2
- data/app/models/collavre/creative/permissible.rb +11 -0
- data/app/models/collavre/creative.rb +29 -4
- data/app/models/collavre/creative_share.rb +109 -4
- data/app/models/collavre/current.rb +1 -0
- data/app/models/collavre/llm_model.rb +62 -0
- data/app/models/collavre/system_setting.rb +12 -1
- data/app/models/collavre/task.rb +121 -2
- data/app/models/collavre/topic.rb +51 -0
- data/app/models/collavre/user.rb +101 -41
- data/app/models/collavre/user_creative_preference.rb +3 -1
- data/app/services/collavre/agent_provisioning/archive.rb +108 -0
- data/app/services/collavre/ai_agent/a2a_dispatcher.rb +16 -3
- data/app/services/collavre/ai_agent/agent_lifecycle_manager.rb +63 -6
- data/app/services/collavre/ai_agent/approval_handler.rb +12 -4
- data/app/services/collavre/ai_agent/claude_channel_adapter.rb +10 -5
- data/app/services/collavre/ai_agent/merged_trigger_comments.rb +164 -0
- data/app/services/collavre/ai_agent/message_builder.rb +103 -16
- data/app/services/collavre/ai_agent/session_provisioner.rb +3 -7
- data/app/services/collavre/ai_agent/task_claim_service.rb +29 -15
- data/app/services/collavre/ai_agent/task_reply_service.rb +67 -0
- data/app/services/collavre/ai_agent_service.rb +108 -20
- data/app/services/collavre/ai_client.rb +211 -21
- data/app/services/collavre/cli_proxy/client.rb +147 -0
- data/app/services/collavre/cli_proxy/endpoint_policy.rb +81 -0
- data/app/services/collavre/cli_proxy/identity.rb +47 -0
- data/app/services/collavre/cli_proxy/safe_net_http_adapter.rb +19 -0
- data/app/services/collavre/command_menu_service.rb +0 -10
- data/app/services/collavre/comment_move_service.rb +96 -7
- data/app/services/collavre/comment_snapshot_restore_service.rb +11 -5
- data/app/services/collavre/comments/action_executor.rb +48 -20
- data/app/services/collavre/comments/command_processor.rb +0 -1
- data/app/services/collavre/comments/list_response.rb +133 -0
- data/app/services/collavre/comments/read_pointer_writer.rb +210 -0
- data/app/services/collavre/comments/read_receipt_broadcaster.rb +157 -0
- data/app/services/collavre/comments/read_receipt_index.rb +152 -0
- data/app/services/collavre/comments/topic_command.rb +72 -1
- data/app/services/collavre/comments/topic_mutation.rb +20 -0
- data/app/services/collavre/creatives/breadcrumb_resolver.rb +1 -1
- data/app/services/collavre/creatives/comment_badge_index.rb +299 -27
- data/app/services/collavre/creatives/reorderer.rb +15 -3
- data/app/services/collavre/creatives/tree_builder.rb +1 -1
- data/app/services/collavre/creatives/tree_formatter.rb +5 -6
- data/app/services/collavre/creatives/workspace_path_resolver.rb +50 -0
- data/app/services/collavre/creatives/workspace_tree_builder.rb +92 -0
- data/app/services/collavre/crons/recurring_topic_tasks.rb +23 -0
- data/app/services/collavre/home_page_path.rb +55 -0
- data/app/services/collavre/html_text.rb +64 -0
- data/app/services/collavre/http_client.rb +11 -2
- data/app/services/collavre/inbox_reply_service.rb +1 -1
- data/app/services/collavre/markdown_converter.rb +13 -1
- data/app/services/collavre/orchestration/agent_orchestrator.rb +776 -81
- data/app/services/collavre/orchestration/arbiter.rb +48 -5
- data/app/services/collavre/orchestration/deferred_trigger_scope.rb +53 -0
- data/app/services/collavre/orchestration/delivery_record.rb +792 -0
- data/app/services/collavre/orchestration/loop_breaker.rb +10 -4
- data/app/services/collavre/orchestration/matcher.rb +132 -17
- data/app/services/collavre/orchestration/policy_resolver.rb +58 -10
- data/app/services/collavre/orchestration/scheduler.rb +10 -4
- data/app/services/collavre/orchestration/scheduling_hooks.rb +10 -0
- data/app/services/collavre/orchestration/selection.rb +43 -0
- data/app/services/collavre/orchestration/stuck_detector.rb +25 -112
- data/app/services/collavre/orchestration/task_coalescer.rb +355 -0
- data/app/services/collavre/orchestration/topic_slot.rb +73 -0
- data/app/services/collavre/system_events/context_builder.rb +60 -11
- data/app/services/collavre/system_events/dispatcher.rb +58 -12
- data/app/services/collavre/system_events/envelope.rb +73 -0
- data/app/services/collavre/system_events/vocabulary.rb +45 -0
- data/app/services/collavre/tools/creative_retrieval_service.rb +1 -1
- data/app/services/collavre/tools/cron_create_service.rb +32 -18
- data/app/services/collavre/tools/id_list.rb +38 -0
- data/app/services/collavre/tools/topic_authorizer.rb +61 -7
- data/app/services/collavre/tools/topic_branch_service.rb +85 -0
- data/app/services/collavre/tools/topic_create_service.rb +117 -0
- data/app/services/collavre/tools/topic_list_service.rb +101 -0
- data/app/services/collavre/tools/topic_message_create_service.rb +192 -0
- data/app/services/collavre/tools/topic_messages_service.rb +346 -0
- data/app/services/collavre/tools/topic_move_service.rb +108 -0
- data/app/services/collavre/tools/topic_selection.rb +59 -0
- data/app/services/collavre/tools/topic_update_service.rb +165 -0
- data/app/services/collavre/topic_branch_service.rb +39 -10
- data/app/services/collavre/topics/agent_resolver.rb +87 -0
- data/app/services/collavre/topics/char_budget.rb +70 -0
- data/app/services/collavre/topics/last_topic_preference_payload.rb +18 -0
- data/app/services/collavre/topics/message_page.rb +380 -0
- data/app/services/collavre/topics/message_page_markdown.rb +102 -0
- data/app/services/collavre/topics/message_scope.rb +61 -0
- data/app/services/collavre/topics/message_stats.rb +92 -0
- data/app/services/collavre/topics/move_blocker.rb +47 -0
- data/app/services/collavre/topics/next_name.rb +35 -0
- data/app/services/collavre/topics/pin_rejection.rb +25 -0
- data/app/services/collavre/topics/read_pointer_relocator.rb +71 -0
- data/app/services/collavre/topics/reserved_name.rb +18 -0
- data/app/services/collavre/topics/serializer.rb +92 -0
- data/app/services/collavre/topics/topic_destroy.rb +30 -0
- data/app/services/collavre/topics/topic_move.rb +80 -0
- data/app/services/collavre/topics/topic_move_effects.rb +35 -0
- data/app/services/collavre/topics/topic_mutation.rb +27 -0
- data/app/views/collavre/admin/integrations/_setting_row.html.erb +5 -8
- data/app/views/collavre/admin/settings/_system_tab.html.erb +7 -0
- data/app/views/collavre/agent_connections/show.html.erb +77 -0
- data/app/views/collavre/agent_gateways/_form.html.erb +71 -0
- data/app/views/collavre/agent_gateways/edit.html.erb +2 -0
- data/app/views/collavre/agent_gateways/index.html.erb +54 -0
- data/app/views/collavre/agent_gateways/new.html.erb +2 -0
- data/app/views/collavre/comments/_comments_popup.html.erb +32 -17
- data/app/views/collavre/comments/_empty_state.html.erb +33 -0
- data/app/views/collavre/comments/_empty_state_minimal.html.erb +7 -0
- data/app/views/collavre/comments/_feature_card.html.erb +35 -0
- data/app/views/collavre/comments/_list.html.erb +3 -2
- data/app/views/collavre/comments/_no_search_results.html.erb +3 -0
- data/app/views/collavre/creatives/_add_button.html.erb +17 -19
- data/app/views/collavre/creatives/_empty_state.html.erb +109 -0
- data/app/views/collavre/creatives/_inline_edit_form.html.erb +5 -5
- data/app/views/collavre/creatives/_tree_loading_placeholder.html.erb +16 -0
- data/app/views/collavre/creatives/_workspace_tree.html.erb +33 -0
- data/app/views/collavre/creatives/index.html.erb +79 -18
- data/app/views/collavre/desktop_setup/show.html.erb +114 -0
- data/app/views/collavre/features/index.html.erb +42 -0
- data/app/views/collavre/features/show.html.erb +52 -0
- data/app/views/collavre/invitation_mailer/invite.html.erb +1 -1
- data/app/views/collavre/invitation_mailer/invite.text.erb +1 -1
- data/app/views/collavre/invites/show.html.erb +14 -1
- data/app/views/collavre/landing/show.html.erb +5 -1
- data/app/views/collavre/shared/_link_creative_modal.html.erb +4 -1
- data/app/views/collavre/shared/navigation/_help_button.html.erb +3 -1
- data/app/views/collavre/shared/navigation/_panels.html.erb +1 -8
- data/app/views/collavre/shared/navigation/_search_form.html.erb +13 -2
- data/app/views/collavre/user_themes/index.html.erb +1 -1
- data/app/views/collavre/users/_llm_fields.html.erb +65 -0
- data/app/views/collavre/users/_org_chart_node.html.erb +1 -1
- data/app/views/collavre/users/edit_ai.html.erb +33 -31
- data/app/views/collavre/users/new.html.erb +4 -2
- data/app/views/collavre/users/new_ai.html.erb +14 -32
- data/app/views/collavre/users/show.html.erb +13 -1
- data/app/views/layouts/collavre/landing.html.erb +9 -4
- data/config/initializers/ruby_llm_openai_reasoning_content.rb +77 -0
- data/config/locales/admin.en.yml +3 -1
- data/config/locales/admin.ko.yml +3 -1
- data/config/locales/agent_gateway.en.yml +217 -0
- data/config/locales/agent_gateway.ko.yml +217 -0
- data/config/locales/ai_agent.en.yml +0 -1
- data/config/locales/ai_agent.ko.yml +0 -1
- data/config/locales/comments.en.yml +72 -30
- data/config/locales/comments.ko.yml +72 -30
- data/config/locales/creatives.en.yml +32 -1
- data/config/locales/creatives.ko.yml +32 -1
- data/config/locales/features.en.yml +140 -0
- data/config/locales/features.ko.yml +140 -0
- data/config/locales/invites.en.yml +3 -0
- data/config/locales/invites.ko.yml +3 -0
- data/config/locales/landing.en.yml +2 -1
- data/config/locales/landing.ko.yml +2 -1
- data/config/locales/tools.en.yml +20 -0
- data/config/locales/tools.ko.yml +20 -0
- data/config/locales/users.en.yml +15 -14
- data/config/locales/users.ko.yml +14 -14
- data/config/routes.rb +45 -19
- data/db/migrate/20250911084338_backfill_creative_in_comment_inbox_items.rb +9 -1
- data/db/migrate/20260724000000_add_creative_id_index_to_comments.rb +34 -0
- data/db/migrate/20260724000001_add_creative_private_id_index_to_comments.rb +50 -0
- data/db/migrate/20260724090000_add_async_comment_notification_state.rb +64 -0
- data/db/migrate/20260727000000_add_waiting_notice_scope_to_comments.rb +30 -0
- data/db/migrate/20260727010000_add_waiting_notice_scope_to_tasks.rb +26 -0
- data/db/migrate/20260805000000_add_creative_workspace_enabled_to_users.rb +5 -0
- data/db/migrate/20260807050000_add_dismissed_notices_to_users.rb +5 -0
- data/db/migrate/20260809000000_change_creative_workspace_enabled_default_to_true.rb +18 -0
- data/db/migrate/20260809000001_create_llm_models.rb +64 -0
- data/db/migrate/20260809000002_create_agent_gateways_and_workspaces.rb +35 -0
- data/db/migrate/20260809000003_hash_agent_workspace_callback_tokens.rb +44 -0
- data/db/migrate/20260809000004_encrypt_agent_workspace_manifest_tokens.rb +59 -0
- data/db/migrate/20260810000000_split_agent_workspace_identity_axes.rb +58 -0
- data/db/migrate/20260810000001_seed_codex_custom_proxy_llm_models.rb +28 -0
- data/db/migrate/20260810010000_add_last_visited_creative_to_users.rb +8 -0
- data/db/migrate/20260810020000_add_last_visited_creative_at_to_users.rb +5 -0
- data/db/migrate/20260810030000_add_last_visited_creative_navigation_order_to_users.rb +6 -0
- data/db/migrate/20260810060000_add_last_visited_creative_issued_sequence_to_users.rb +5 -0
- data/db/migrate/20260810100000_add_desktop_managed_to_agent_gateways.rb +40 -0
- data/db/migrate/20260810100001_add_desktop_preset_adapter_to_users.rb +38 -0
- data/db/migrate/20260811000000_make_agent_gateway_completion_key_nullable.rb +7 -0
- data/db/migrate/20260812000000_remove_workflow_columns_from_tasks.rb +101 -0
- data/db/migrate/20260812000001_remove_android_devices.rb +24 -0
- data/db/migrate/20260812010000_drop_inbox_items.rb +22 -0
- data/db/migrate/20260813000000_clear_claude_channel_presence_routing_expressions.rb +16 -0
- data/db/migrate/20260813000001_add_last_topic_revision_to_user_creative_preferences.rb +5 -0
- data/db/migrate/20260813000002_add_last_topic_save_order_to_user_creative_preferences.rb +6 -0
- data/db/migrate/20260813000003_add_last_topic_save_sequences_to_user_creative_preferences.rb +5 -0
- data/db/migrate/20260813000004_add_last_topic_save_fences_to_user_creative_preferences.rb +6 -0
- data/db/migrate/20260814000000_add_topic_to_comment_read_pointers.rb +79 -0
- data/db/migrate/20260814000001_add_creative_topic_private_id_index_to_comments.rb +37 -0
- data/db/migrate/20260816000000_add_watermark_index_to_comment_read_pointers.rb +27 -0
- data/db/migrate/20260816000001_add_private_recipient_topic_index_to_comments.rb +48 -0
- data/db/migrate/20260820000000_add_topic_assigned_at_to_comments.rb +13 -0
- data/db/seeds.rb +1 -51
- data/lib/collavre/engine.rb +108 -1
- data/lib/collavre/feature_card_registry.rb +168 -0
- data/lib/collavre/hashed_access_token_lookup.rb +22 -0
- data/lib/collavre/sensitive_request_silencer.rb +23 -0
- data/lib/collavre/user_extensions.rb +0 -2
- data/lib/collavre/version.rb +1 -1
- data/lib/collavre.rb +1 -0
- data/skills/collavre/SKILL.md +165 -0
- data/skills/collavre/package.json +8 -0
- data/skills/collavre/references/tool-reference.md +347 -0
- data/skills/collavre/scripts/collavre +634 -0
- data/skills/collavre/scripts/html_entities.js +24 -0
- metadata +228 -30
- data/app/controllers/collavre/api/v1/mobile/agent_events_controller.rb +0 -224
- data/app/controllers/collavre/api/v1/mobile/base_controller.rb +0 -95
- data/app/controllers/collavre/api/v1/mobile/devices_controller.rb +0 -31
- data/app/controllers/collavre/api/v1/mobile/voice_commands_controller.rb +0 -25
- data/app/controllers/collavre/inbox_items_controller.rb +0 -70
- data/app/controllers/collavre/typo_corrections_controller.rb +0 -39
- data/app/javascript/lib/__tests__/typo_correction.test.js +0 -192
- data/app/javascript/lib/typo_correction.js +0 -146
- data/app/javascript/modules/__tests__/typo_corrector.test.js +0 -365
- data/app/javascript/modules/creative_guide.js +0 -67
- data/app/javascript/modules/typo_corrector.js +0 -534
- data/app/jobs/collavre/inbox_summary_job.rb +0 -24
- data/app/mailers/collavre/inbox_mailer.rb +0 -19
- data/app/models/collavre/inbox_item.rb +0 -89
- data/app/services/collavre/comments/concerns/workflow_support.rb +0 -115
- data/app/services/collavre/comments/work_command.rb +0 -161
- data/app/services/collavre/comments/workflow_executor.rb +0 -276
- data/app/services/collavre/gemini_parent_recommender.rb +0 -77
- data/app/services/collavre/mobile/event_summarizer.rb +0 -40
- data/app/services/collavre/typo_corrector.rb +0 -188
- data/app/views/collavre/inbox_items/_item.html.erb +0 -18
- data/app/views/collavre/inbox_items/_items.html.erb +0 -3
- data/app/views/collavre/inbox_items/_list.html.erb +0 -7
- data/app/views/collavre/inbox_items/index.html.erb +0 -1
- data/app/views/collavre/inbox_mailer/daily_summary.html.erb +0 -11
- data/app/views/collavre/inbox_mailer/daily_summary.text.erb +0 -8
- data/app/views/collavre/users/typo_correction.html.erb +0 -50
- data/config/locales/mobile.en.yml +0 -16
- data/config/locales/mobile.ko.yml +0 -16
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { creativeIdFrom } from './creative_tree_dom';
|
|
2
|
+
|
|
3
|
+
export function createDelegatedClickHandler(session) {
|
|
4
|
+
return function handleDelegatedClick(event) {
|
|
5
|
+
const target = event.target;
|
|
6
|
+
if (!target || typeof target.closest !== 'function') return;
|
|
7
|
+
|
|
8
|
+
const editBtn = target.closest('.edit-inline-btn');
|
|
9
|
+
if (editBtn) {
|
|
10
|
+
event.preventDefault();
|
|
11
|
+
const tree = editBtn.closest('.creative-tree');
|
|
12
|
+
if (tree) session.handleEditButtonClick(tree);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const addBtn = target.closest('.add-creative-btn:not(#inline-add):not(#inline-level-down):not(#inline-level-up)');
|
|
17
|
+
if (addBtn) {
|
|
18
|
+
event.preventDefault();
|
|
19
|
+
if (session.template.style.display === 'block') {
|
|
20
|
+
session.hideCurrent();
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const tree = addBtn.closest('.creative-tree');
|
|
25
|
+
let parentId;
|
|
26
|
+
let container;
|
|
27
|
+
let insertBefore;
|
|
28
|
+
let beforeId = '';
|
|
29
|
+
|
|
30
|
+
if (tree) {
|
|
31
|
+
parentId = tree.dataset.id;
|
|
32
|
+
container = tree.querySelector('.creative-children');
|
|
33
|
+
if (!container) {
|
|
34
|
+
container = document.createElement('div');
|
|
35
|
+
container.className = 'creative-children';
|
|
36
|
+
container.id = `creative-children-${parentId}`;
|
|
37
|
+
tree.appendChild(container);
|
|
38
|
+
}
|
|
39
|
+
insertBefore = container.firstElementChild;
|
|
40
|
+
beforeId = insertBefore ? creativeIdFrom(insertBefore) : '';
|
|
41
|
+
} else {
|
|
42
|
+
parentId = addBtn.dataset.parentId || '';
|
|
43
|
+
container = document.getElementById('creatives');
|
|
44
|
+
if (!container) return;
|
|
45
|
+
insertBefore = container.firstElementChild;
|
|
46
|
+
beforeId = insertBefore ? creativeIdFrom(insertBefore) : '';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
session.startNew(parentId, container, insertBefore, beforeId);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const newRootBtn = target.closest('.new-root-creative-btn');
|
|
54
|
+
if (newRootBtn) {
|
|
55
|
+
event.preventDefault();
|
|
56
|
+
const container = document.getElementById('creatives');
|
|
57
|
+
if (!container) return;
|
|
58
|
+
|
|
59
|
+
if (session.template.style.display === 'block') {
|
|
60
|
+
session.hideCurrent();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const insertBefore = container.firstElementChild;
|
|
65
|
+
const beforeId = insertBefore ? creativeIdFrom(insertBefore) : '';
|
|
66
|
+
session.startNew('', container, insertBefore, beforeId);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const appendParentBtn = target.closest('.append-parent-btn');
|
|
71
|
+
if (!appendParentBtn) return;
|
|
72
|
+
|
|
73
|
+
event.preventDefault();
|
|
74
|
+
const targetId = appendParentBtn.dataset.childId;
|
|
75
|
+
const targetTree = document.getElementById(`creative-${targetId}`);
|
|
76
|
+
if (!targetTree) return;
|
|
77
|
+
const container = targetTree.parentNode;
|
|
78
|
+
session.startNew(
|
|
79
|
+
container.id.startsWith('creative-children-') ? container.id.replace('creative-children-', '') : '',
|
|
80
|
+
container,
|
|
81
|
+
targetTree,
|
|
82
|
+
targetId,
|
|
83
|
+
'',
|
|
84
|
+
targetId
|
|
85
|
+
);
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
// apply here. The only DOM writes are element mutations (insertBefore, appendChild,
|
|
22
22
|
// setAttribute, dataset, style, requestUpdate) invoked on the passed nodes.
|
|
23
23
|
import { treeRowElement, readRowLevel, buildChildrenLoadUrl } from './creative_row_editor_helpers';
|
|
24
|
+
import { restoreTreeEmptyState } from './creative_tree_empty_state';
|
|
24
25
|
|
|
25
26
|
export function creativeTreeElement(node) {
|
|
26
27
|
if (!node) return null;
|
|
@@ -257,4 +258,7 @@ export function removeTreeElement(tree) {
|
|
|
257
258
|
} else if (tree.remove) {
|
|
258
259
|
tree.remove();
|
|
259
260
|
}
|
|
261
|
+
// Covers every editor-side removal: abandoned new row, delete, level moves that
|
|
262
|
+
// empty the tree. No-op while any row remains.
|
|
263
|
+
restoreTreeEmptyState();
|
|
260
264
|
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Empty-state placeholder ("No sub-creatives found." / "No creatives found.")
|
|
2
|
+
// lifecycle for the creative tree.
|
|
3
|
+
//
|
|
4
|
+
// The placeholder is server-rendered inside #creatives (see creatives/index.html.erb)
|
|
5
|
+
// and marked with data-creatives-empty-state. Rows, however, are inserted into that
|
|
6
|
+
// same container purely client-side — by the inline row editor (local add) and by the
|
|
7
|
+
// websocket broadcast handler (another user / MCP add). Neither path re-renders the
|
|
8
|
+
// container, so without an explicit hook the placeholder stays visible next to the
|
|
9
|
+
// freshly created row.
|
|
10
|
+
//
|
|
11
|
+
// hideTreeEmptyState() hides the placeholder as soon as a row lands;
|
|
12
|
+
// restoreTreeEmptyState() shows it again when the last row leaves.
|
|
13
|
+
//
|
|
14
|
+
// The placeholder is toggled rather than removed and re-created: keeping the original
|
|
15
|
+
// node means no markup has to be cached and re-parsed as HTML (which would both
|
|
16
|
+
// re-introduce an HTML sink and lose the server-rendered button_to CSRF token in the
|
|
17
|
+
// "request permission" variant of the placeholder).
|
|
18
|
+
//
|
|
19
|
+
// The container itself is wiped on every client-side tree load (tree_controller's
|
|
20
|
+
// showLoadingIndicator / renderCreativeTree), so on a populated tree the placeholder
|
|
21
|
+
// is gone by the time the rows arrive. #creatives-empty-state-template holds a
|
|
22
|
+
// pristine copy outside the container; restoreTreeEmptyState() clones it when the
|
|
23
|
+
// tree empties out and nothing is left to un-hide. Cloning a <template> is not an
|
|
24
|
+
// HTML sink — the browser parsed the markup at page load, server-side-escaped.
|
|
25
|
+
//
|
|
26
|
+
// One more wrinkle: the "Chats" feed is paginated, so an empty container does not
|
|
27
|
+
// mean an empty result set — there may be further pages waiting behind the
|
|
28
|
+
// load-more sentinel. tree_controller marks the container with
|
|
29
|
+
// data-creatives-pagination-pending while that is the case, and restoreTreeEmptyState()
|
|
30
|
+
// stays out of the way until the last page has landed.
|
|
31
|
+
const EMPTY_STATE_SELECTOR = '[data-creatives-empty-state]';
|
|
32
|
+
const EMPTY_STATE_TEMPLATE_ID = 'creatives-empty-state-template';
|
|
33
|
+
export const PAGINATION_PENDING_ATTRIBUTE = 'data-creatives-pagination-pending';
|
|
34
|
+
|
|
35
|
+
export function creativeTreeContainer() {
|
|
36
|
+
return document.getElementById('creatives');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function emptyStateElements(container) {
|
|
40
|
+
if (!container || !container.querySelectorAll) return [];
|
|
41
|
+
return Array.from(container.querySelectorAll(EMPTY_STATE_SELECTOR));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function hideTreeEmptyState(container = creativeTreeContainer()) {
|
|
45
|
+
emptyStateElements(container).forEach((element) => {
|
|
46
|
+
element.hidden = true;
|
|
47
|
+
// Belt and braces: `hidden` is a UA-stylesheet rule that any `display` rule
|
|
48
|
+
// on the element would win over.
|
|
49
|
+
element.style.display = 'none';
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function clonedEmptyStatePlaceholder() {
|
|
54
|
+
const template = document.getElementById(EMPTY_STATE_TEMPLATE_ID);
|
|
55
|
+
if (!template || !template.content) return null;
|
|
56
|
+
return template.content.cloneNode(true).querySelector(EMPTY_STATE_SELECTOR);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function restoreTreeEmptyState(container = creativeTreeContainer()) {
|
|
60
|
+
if (!container || !container.querySelector) return;
|
|
61
|
+
// Still populated — the placeholder stays hidden.
|
|
62
|
+
if (container.querySelector('creative-tree-row')) return;
|
|
63
|
+
// Rendered rows are gone but the feed has further pages queued: the list is not
|
|
64
|
+
// empty, it is just between pages. Showing "No creatives found." here would both
|
|
65
|
+
// lie and survive the append, sitting above the rows the next page brings in.
|
|
66
|
+
if (container.hasAttribute && container.hasAttribute(PAGINATION_PENDING_ATTRIBUTE)) return;
|
|
67
|
+
|
|
68
|
+
const existing = emptyStateElements(container);
|
|
69
|
+
if (existing.length > 0) {
|
|
70
|
+
existing.forEach((element) => {
|
|
71
|
+
element.hidden = false;
|
|
72
|
+
element.style.removeProperty('display');
|
|
73
|
+
});
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Nothing to un-hide — the tree render wiped the container. Re-attach a copy.
|
|
78
|
+
const placeholder = clonedEmptyStatePlaceholder();
|
|
79
|
+
if (placeholder) container.appendChild(placeholder);
|
|
80
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function createListenerRegistry() {
|
|
2
|
+
const bindings = [];
|
|
3
|
+
|
|
4
|
+
return {
|
|
5
|
+
add(target, type, handler, options) {
|
|
6
|
+
if (!target || typeof target.addEventListener !== 'function') return;
|
|
7
|
+
|
|
8
|
+
target.addEventListener(type, handler, options);
|
|
9
|
+
bindings.push({ target, type, handler, options });
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
releaseAll() {
|
|
13
|
+
while (bindings.length > 0) {
|
|
14
|
+
const { target, type, handler, options } = bindings.pop();
|
|
15
|
+
target.removeEventListener(type, handler, options);
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
get size() {
|
|
20
|
+
return bindings.length;
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import CommonPopup from '../lib/common_popup'
|
|
2
|
-
import {
|
|
2
|
+
import { caretAnchor } from '../utils/caret_position'
|
|
3
3
|
|
|
4
4
|
let mentionMenuInitialized = false
|
|
5
5
|
|
|
@@ -42,8 +42,7 @@ if (!mentionMenuInitialized) {
|
|
|
42
42
|
return
|
|
43
43
|
}
|
|
44
44
|
popupMenu.setItems(users)
|
|
45
|
-
|
|
46
|
-
popupMenu.showAt(caretRect)
|
|
45
|
+
popupMenu.showAt(caretAnchor(textarea))
|
|
47
46
|
}
|
|
48
47
|
|
|
49
48
|
textarea.addEventListener('keydown', function (event) {
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
import { caretAnchor } from '../caret_position'
|
|
5
|
+
|
|
6
|
+
// caretAnchor is what a caret-anchored popup hands to CommonPopup.showAt in
|
|
7
|
+
// place of a rect: the caret is not an element, so the only way placement can
|
|
8
|
+
// re-measure it later is to re-derive it from the input each time.
|
|
9
|
+
describe('caretAnchor', () => {
|
|
10
|
+
let textarea
|
|
11
|
+
|
|
12
|
+
// jsdom measures every element as a zeroed rect, so the mirror-div path in
|
|
13
|
+
// getCaretClientRect cannot produce distinguishable coordinates. Dropping
|
|
14
|
+
// selectionStart takes the documented fallback branch instead, where the
|
|
15
|
+
// rect is the input's own and therefore stubbable.
|
|
16
|
+
const useInputRectFallback = () => {
|
|
17
|
+
Object.defineProperty(textarea, 'selectionStart', { value: null, configurable: true })
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
textarea = document.createElement('textarea')
|
|
22
|
+
textarea.value = 'hello'
|
|
23
|
+
document.body.appendChild(textarea)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
document.body.innerHTML = ''
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test('re-derives the rect on every call, so a moved input moves the anchor', () => {
|
|
31
|
+
useInputRectFallback()
|
|
32
|
+
const anchor = caretAnchor(textarea)
|
|
33
|
+
|
|
34
|
+
textarea.getBoundingClientRect = () => ({ left: 10, top: 500, bottom: 540 })
|
|
35
|
+
const before = anchor()
|
|
36
|
+
// The keyboard opens and the composer is lifted clear of it.
|
|
37
|
+
textarea.getBoundingClientRect = () => ({ left: 10, top: 200, bottom: 240 })
|
|
38
|
+
const after = anchor()
|
|
39
|
+
|
|
40
|
+
expect(before.top).toBe(500)
|
|
41
|
+
expect(after.top).toBe(200)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('falls back to the input rect when the caret cannot be located', () => {
|
|
45
|
+
const inputRect = { left: 10, top: 200, bottom: 240 }
|
|
46
|
+
textarea.getBoundingClientRect = () => inputRect
|
|
47
|
+
useInputRectFallback()
|
|
48
|
+
|
|
49
|
+
expect(caretAnchor(textarea)()).toBe(inputRect)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
test('measures the caret, not the whole input, when the caret is locatable', () => {
|
|
53
|
+
const inputRect = { left: 10, top: 200, bottom: 240 }
|
|
54
|
+
textarea.getBoundingClientRect = () => inputRect
|
|
55
|
+
textarea.setSelectionRange(3, 3)
|
|
56
|
+
|
|
57
|
+
const rect = caretAnchor(textarea)()
|
|
58
|
+
|
|
59
|
+
expect(rect).not.toBe(inputRect)
|
|
60
|
+
expect(rect).not.toBeNull()
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('returns nothing once the input has left the document', () => {
|
|
64
|
+
const anchor = caretAnchor(textarea)
|
|
65
|
+
textarea.remove()
|
|
66
|
+
|
|
67
|
+
// A detached input measures as all zeros, which would pin the popup to
|
|
68
|
+
// the top-left corner; CommonPopup keeps its last rect on a null.
|
|
69
|
+
expect(anchor()).toBeNull()
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test('returns nothing when there is no input at all', () => {
|
|
73
|
+
expect(caretAnchor(null)()).toBeNull()
|
|
74
|
+
})
|
|
75
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { CHEVRON_COLLAPSED, CHEVRON_EXPANDED } from '../chevron_icons'
|
|
2
|
+
|
|
3
|
+
describe('tree chevron icons', () => {
|
|
4
|
+
test('shares consistent SVG attributes and the expected toggle directions', () => {
|
|
5
|
+
const collapsed = document.createElement('template')
|
|
6
|
+
collapsed.innerHTML = CHEVRON_COLLAPSED
|
|
7
|
+
const expanded = document.createElement('template')
|
|
8
|
+
expanded.innerHTML = CHEVRON_EXPANDED
|
|
9
|
+
|
|
10
|
+
expect(collapsed.content.querySelector('svg').getAttribute('aria-hidden')).toBe('true')
|
|
11
|
+
expect(collapsed.content.querySelector('path').getAttribute('d')).toBe('M9 6L15 12L9 18')
|
|
12
|
+
expect(expanded.content.querySelector('svg').getAttribute('aria-hidden')).toBe('true')
|
|
13
|
+
expect(expanded.content.querySelector('path').getAttribute('d')).toBe('M6 9L12 15L18 9')
|
|
14
|
+
})
|
|
15
|
+
})
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
// Anchor provider for a popup hung off the caret of `input`, for CommonPopup's
|
|
2
|
+
// showAt. The caret has no element to hold on to, so its rect has to be
|
|
3
|
+
// re-derived from the input's live geometry every time placement runs.
|
|
4
|
+
export function caretAnchor(input) {
|
|
5
|
+
return () => {
|
|
6
|
+
if (!input?.isConnected) return null
|
|
7
|
+
return getCaretClientRect(input) || input.getBoundingClientRect()
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
1
11
|
export function getCaretClientRect(input) {
|
|
2
12
|
if (!input || typeof input.selectionStart !== 'number') return null
|
|
3
13
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
const CHEVRON_ATTRIBUTES =
|
|
2
|
+
'width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"'
|
|
3
|
+
|
|
4
|
+
export const CHEVRON_COLLAPSED =
|
|
5
|
+
`<svg ${CHEVRON_ATTRIBUTES}><path d="M9 6L15 12L9 18"/></svg>`
|
|
6
|
+
export const CHEVRON_EXPANDED =
|
|
7
|
+
`<svg ${CHEVRON_ATTRIBUTES}><path d="M6 9L12 15L18 9"/></svg>`
|