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,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { jest } from '@jest/globals'
|
|
6
|
+
|
|
7
|
+
const csrfFetch = jest.fn()
|
|
8
|
+
const alertDialog = jest.fn(() => Promise.resolve())
|
|
9
|
+
|
|
10
|
+
jest.unstable_mockModule('../../../lib/api/csrf_fetch', () => ({
|
|
11
|
+
default: csrfFetch,
|
|
12
|
+
}))
|
|
13
|
+
jest.unstable_mockModule('../../../lib/utils/dialog', () => ({
|
|
14
|
+
alertDialog,
|
|
15
|
+
}))
|
|
16
|
+
|
|
17
|
+
const { Application } = await import('@hotwired/stimulus')
|
|
18
|
+
const WriteAccessRequestController = (await import('../write_access_request_controller')).default
|
|
19
|
+
|
|
20
|
+
describe('CreativesWriteAccessRequestController', () => {
|
|
21
|
+
const failureMessage = '권한을 요청하지 못했습니다.'
|
|
22
|
+
let application
|
|
23
|
+
let container
|
|
24
|
+
let button
|
|
25
|
+
|
|
26
|
+
function markup() {
|
|
27
|
+
return `
|
|
28
|
+
<div data-controller="creatives--write-access-request" data-creatives--write-access-request-url-value="/creatives/1/request_permission" data-creatives--write-access-request-failure-message-value="${failureMessage}">
|
|
29
|
+
<button type="button" data-creatives--write-access-request-target="button" data-action="click->creatives--write-access-request#request">Request write access</button>
|
|
30
|
+
<span data-creatives--write-access-request-target="pending" hidden>Access requested</span>
|
|
31
|
+
</div>
|
|
32
|
+
`
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
csrfFetch.mockReset()
|
|
37
|
+
alertDialog.mockClear()
|
|
38
|
+
document.body.innerHTML = markup()
|
|
39
|
+
container = document.querySelector('[data-controller="creatives--write-access-request"]')
|
|
40
|
+
button = container.querySelector('[data-creatives--write-access-request-target="button"]')
|
|
41
|
+
|
|
42
|
+
application = Application.start()
|
|
43
|
+
application.register('creatives--write-access-request', WriteAccessRequestController)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
afterEach(() => {
|
|
47
|
+
application.stop()
|
|
48
|
+
document.body.innerHTML = ''
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test('on success, hides the button and shows the pending pill', async () => {
|
|
52
|
+
csrfFetch.mockResolvedValue({ ok: true })
|
|
53
|
+
|
|
54
|
+
button.click()
|
|
55
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
56
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
57
|
+
|
|
58
|
+
expect(csrfFetch).toHaveBeenCalledWith('/creatives/1/request_permission', { method: 'POST' })
|
|
59
|
+
expect(button.hidden).toBe(true)
|
|
60
|
+
const pending = container.querySelector('[data-creatives--write-access-request-target="pending"]')
|
|
61
|
+
expect(pending.hidden).toBe(false)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test('on non-ok response, re-enables the button and does not show the pending pill', async () => {
|
|
65
|
+
csrfFetch.mockResolvedValue({ ok: false })
|
|
66
|
+
|
|
67
|
+
button.click()
|
|
68
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
69
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
70
|
+
|
|
71
|
+
expect(button.hidden).toBe(false)
|
|
72
|
+
expect(button.disabled).toBe(false)
|
|
73
|
+
const pending = container.querySelector('[data-creatives--write-access-request-target="pending"]')
|
|
74
|
+
expect(pending.hidden).toBe(true)
|
|
75
|
+
expect(alertDialog).toHaveBeenCalledWith(failureMessage)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
test('on network error, re-enables the button', async () => {
|
|
79
|
+
csrfFetch.mockRejectedValue(new Error('network down'))
|
|
80
|
+
|
|
81
|
+
button.click()
|
|
82
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
83
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
84
|
+
|
|
85
|
+
expect(button.hidden).toBe(false)
|
|
86
|
+
expect(button.disabled).toBe(false)
|
|
87
|
+
expect(alertDialog).toHaveBeenCalledWith(failureMessage)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
test('does not fall back to hardcoded copy when no localized failure message is rendered', async () => {
|
|
91
|
+
container.removeAttribute('data-creatives--write-access-request-failure-message-value')
|
|
92
|
+
csrfFetch.mockResolvedValue({ ok: false })
|
|
93
|
+
|
|
94
|
+
button.click()
|
|
95
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
96
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
97
|
+
|
|
98
|
+
expect(button.disabled).toBe(false)
|
|
99
|
+
expect(alertDialog).not.toHaveBeenCalled()
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
test('on a followed redirect to the sign-in page (expired session), navigates there instead of showing pending', async () => {
|
|
103
|
+
// fetch() follows redirects by default, so when the session has expired,
|
|
104
|
+
// Authentication#request_authentication redirects to the sign-in page and
|
|
105
|
+
// the browser lands on a 200 OK response for the login form itself. `ok`
|
|
106
|
+
// is therefore true even though no permission request was ever created —
|
|
107
|
+
// `redirected` is what actually distinguishes this from a real success.
|
|
108
|
+
const redirectSpy = jest
|
|
109
|
+
.spyOn(WriteAccessRequestController.prototype, 'redirectToSignIn')
|
|
110
|
+
.mockImplementation(() => {})
|
|
111
|
+
|
|
112
|
+
csrfFetch.mockResolvedValue({ ok: true, redirected: true, url: 'http://localhost/session/new' })
|
|
113
|
+
|
|
114
|
+
button.click()
|
|
115
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
116
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
117
|
+
|
|
118
|
+
expect(redirectSpy).toHaveBeenCalledWith('http://localhost/session/new')
|
|
119
|
+
expect(button.hidden).toBe(false)
|
|
120
|
+
const pending = container.querySelector('[data-creatives--write-access-request-target="pending"]')
|
|
121
|
+
expect(pending.hidden).toBe(true)
|
|
122
|
+
|
|
123
|
+
redirectSpy.mockRestore()
|
|
124
|
+
})
|
|
125
|
+
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus'
|
|
2
2
|
import csrfFetch from '../../lib/api/csrf_fetch'
|
|
3
3
|
import { confirmDialog } from '../../lib/utils/confirm_dialog'
|
|
4
|
+
import { removeTreeElement } from '../../modules/creative_tree_dom'
|
|
4
5
|
|
|
5
6
|
export default class extends Controller {
|
|
6
7
|
static targets = [
|
|
@@ -102,9 +103,13 @@ export default class extends Controller {
|
|
|
102
103
|
detail: { creativeIds: ids.map(String) }
|
|
103
104
|
}))
|
|
104
105
|
|
|
106
|
+
// Route through the shared helper: it drops the enclosing <creative-tree-row>
|
|
107
|
+
// wrapper (removing only the inner .creative-tree left an empty wrapper behind,
|
|
108
|
+
// which reads as "still populated") and restores the empty-state placeholder
|
|
109
|
+
// once the last row is gone. The destroy broadcast excludes the initiating
|
|
110
|
+
// user, so nothing else repairs this window's DOM.
|
|
105
111
|
ids.forEach((id) => {
|
|
106
|
-
|
|
107
|
-
if (tree) tree.remove()
|
|
112
|
+
removeTreeElement(document.getElementById(`creative-${id}`))
|
|
108
113
|
})
|
|
109
114
|
|
|
110
115
|
this.clearSelection()
|
|
@@ -1,19 +1,34 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus'
|
|
2
2
|
import { renderCreativeTree, appendCreativeNodes, dispatchCreativeTreeUpdated } from '../../creatives/tree_renderer'
|
|
3
3
|
import { parseEmojis } from '../../utils/emoji_parser'
|
|
4
|
+
import {
|
|
5
|
+
hideTreeEmptyState,
|
|
6
|
+
restoreTreeEmptyState,
|
|
7
|
+
PAGINATION_PENDING_ATTRIBUTE,
|
|
8
|
+
} from '../../modules/creative_tree_empty_state'
|
|
4
9
|
|
|
5
10
|
const TREE_RETRY_DELAYS_MS = [200, 600]
|
|
6
11
|
|
|
7
12
|
export default class extends Controller {
|
|
8
13
|
static values = {
|
|
9
14
|
url: String,
|
|
10
|
-
|
|
15
|
+
// Neither fallback is an HTML string any more: showEmptyState() clones the
|
|
16
|
+
// server-rendered <template>, and the load-error fallback is a bare
|
|
17
|
+
// translated sentence the client wraps in a <p> itself. Reading markup back
|
|
18
|
+
// out of a data attribute and assigning it is an innerHTML sink as far as
|
|
19
|
+
// CodeQL (js/xss-through-dom) is concerned, and neither needs to be one.
|
|
20
|
+
errorText: String,
|
|
21
|
+
// Accessible name for a loading placeholder built by this controller.
|
|
22
|
+
// Renderers that do not provide a translated server placeholder must pass
|
|
23
|
+
// this value so extensions cannot expose a hardcoded label in another locale.
|
|
24
|
+
loadingText: String,
|
|
11
25
|
}
|
|
12
26
|
|
|
13
27
|
connect() {
|
|
14
28
|
this.abortController = null
|
|
15
29
|
this.loadingIndicator = null
|
|
16
30
|
this._editing = false
|
|
31
|
+
this._reloadHoldCount = 0
|
|
17
32
|
this._pagination = null
|
|
18
33
|
this._sentinel = null
|
|
19
34
|
this._sentinelObserver = null
|
|
@@ -27,10 +42,7 @@ export default class extends Controller {
|
|
|
27
42
|
this._editing = false
|
|
28
43
|
// Apply any pending sync data that was deferred while editing
|
|
29
44
|
this._applyPendingSyncData()
|
|
30
|
-
|
|
31
|
-
this._pendingRefetch = false
|
|
32
|
-
this.debouncedLoad()
|
|
33
|
-
}
|
|
45
|
+
this._drainPendingReload()
|
|
34
46
|
}
|
|
35
47
|
document.documentElement.classList.remove('creative-alignment-ready')
|
|
36
48
|
if (!this.hasCachedContent()) {
|
|
@@ -41,13 +53,7 @@ export default class extends Controller {
|
|
|
41
53
|
this.element.addEventListener('creative-tree:updated', this.handleTreeUpdated)
|
|
42
54
|
document.addEventListener('creative-editing:start', this._handleEditStart)
|
|
43
55
|
document.addEventListener('creative-editing:stop', this._handleEditStop)
|
|
44
|
-
this._handleSyncRefetch = () =>
|
|
45
|
-
if (this._editing) {
|
|
46
|
-
this._pendingRefetch = true
|
|
47
|
-
return
|
|
48
|
-
}
|
|
49
|
-
this.debouncedLoad()
|
|
50
|
-
}
|
|
56
|
+
this._handleSyncRefetch = () => this.requestReload()
|
|
51
57
|
document.addEventListener('creative-sync:refetch', this._handleSyncRefetch)
|
|
52
58
|
this._setupArchiveToggle()
|
|
53
59
|
}
|
|
@@ -141,7 +147,57 @@ export default class extends Controller {
|
|
|
141
147
|
|
|
142
148
|
debouncedLoad() {
|
|
143
149
|
if (this._debouncedLoadTimer) clearTimeout(this._debouncedLoadTimer)
|
|
144
|
-
this._debouncedLoadTimer = setTimeout(() =>
|
|
150
|
+
this._debouncedLoadTimer = setTimeout(() => {
|
|
151
|
+
// Re-check rather than trusting the check requestReload() already made.
|
|
152
|
+
// Switching rows is a `creative-editing:stop` immediately followed by a
|
|
153
|
+
// `creative-editing:start`, so a refetch drained on the stop is still
|
|
154
|
+
// sitting in this debounce window when the next row opens — and load()
|
|
155
|
+
// replaces the whole container, which would take that row, the editor
|
|
156
|
+
// attached inside it and the unsaved draft out of the document. Re-pend
|
|
157
|
+
// instead of dropping it: the reload is still owed, just not yet safe.
|
|
158
|
+
if (this._editing || this._reloadHoldCount > 0) {
|
|
159
|
+
this._pendingRefetch = true
|
|
160
|
+
return
|
|
161
|
+
}
|
|
162
|
+
this.load()
|
|
163
|
+
}, 300)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Editing-aware reload — the entry point for anything that wants the tree
|
|
167
|
+
// refetched in response to something that already happened elsewhere (the sync
|
|
168
|
+
// channel, an archive/unarchive that landed, a delete that promoted children to
|
|
169
|
+
// the root). load() replaces the whole container, which takes the row an open
|
|
170
|
+
// editor is attached to out of the document along with the unsaved draft inside
|
|
171
|
+
// it; those callers can land at any moment, including long after the user has
|
|
172
|
+
// moved on to editing a different row. So the reload waits for
|
|
173
|
+
// `creative-editing:stop`, which _handleEditStop drains.
|
|
174
|
+
//
|
|
175
|
+
// Call load() directly only for reloads the user just asked for and is waiting
|
|
176
|
+
// on (filter change, archive toggle), where re-rendering is the point.
|
|
177
|
+
requestReload() {
|
|
178
|
+
if (this._editing || this._reloadHoldCount > 0) {
|
|
179
|
+
this._pendingRefetch = true
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
this.debouncedLoad()
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Keep editing-aware reloads pending while an operation is between its local
|
|
186
|
+
// edit flush and its server response. The counter makes overlapping requests
|
|
187
|
+
// release independently without allowing an early reload between them.
|
|
188
|
+
beginReloadHold() {
|
|
189
|
+
this._reloadHoldCount += 1
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
endReloadHold() {
|
|
193
|
+
if (this._reloadHoldCount > 0) this._reloadHoldCount -= 1
|
|
194
|
+
this._drainPendingReload()
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
_drainPendingReload() {
|
|
198
|
+
if (!this._pendingRefetch || this._editing || this._reloadHoldCount > 0) return
|
|
199
|
+
this._pendingRefetch = false
|
|
200
|
+
this.debouncedLoad()
|
|
145
201
|
}
|
|
146
202
|
|
|
147
203
|
load() {
|
|
@@ -187,7 +243,7 @@ export default class extends Controller {
|
|
|
187
243
|
}
|
|
188
244
|
console.error(error)
|
|
189
245
|
this.hideLoadingIndicator()
|
|
190
|
-
this.
|
|
246
|
+
this.showErrorState()
|
|
191
247
|
})
|
|
192
248
|
}
|
|
193
249
|
|
|
@@ -221,6 +277,9 @@ export default class extends Controller {
|
|
|
221
277
|
this._pagination = pagination || null
|
|
222
278
|
if (!this._pagination || !this._pagination.has_more) return
|
|
223
279
|
if (typeof IntersectionObserver === 'undefined') return
|
|
280
|
+
// Deleting every rendered row does not empty the feed while further pages are
|
|
281
|
+
// queued, so suppress the empty-state placeholder until the last page lands.
|
|
282
|
+
this.element.setAttribute(PAGINATION_PENDING_ATTRIBUTE, 'true')
|
|
224
283
|
this._createSentinel()
|
|
225
284
|
}
|
|
226
285
|
|
|
@@ -266,6 +325,9 @@ export default class extends Controller {
|
|
|
266
325
|
this._hideLoadMoreIndicator()
|
|
267
326
|
const nodes = Array.isArray(data?.creatives) ? data.creatives : []
|
|
268
327
|
if (nodes.length > 0) {
|
|
328
|
+
// Belt and braces alongside the pagination-pending guard: a placeholder
|
|
329
|
+
// that slipped through must never sit above the rows being appended.
|
|
330
|
+
hideTreeEmptyState(this.element)
|
|
269
331
|
appendCreativeNodes(this.element, nodes)
|
|
270
332
|
dispatchCreativeTreeUpdated(this.element)
|
|
271
333
|
this.queueAlignmentUpdate()
|
|
@@ -276,6 +338,10 @@ export default class extends Controller {
|
|
|
276
338
|
this._repositionSentinel()
|
|
277
339
|
} else {
|
|
278
340
|
this._teardownPagination()
|
|
341
|
+
// Last page in, and the rows that were on screen when it was requested
|
|
342
|
+
// may since have been deleted. Nothing is pending any more, so an empty
|
|
343
|
+
// container now genuinely is an empty feed.
|
|
344
|
+
restoreTreeEmptyState(this.element)
|
|
279
345
|
}
|
|
280
346
|
})
|
|
281
347
|
.catch((error) => {
|
|
@@ -298,6 +364,7 @@ export default class extends Controller {
|
|
|
298
364
|
}
|
|
299
365
|
|
|
300
366
|
_teardownPagination() {
|
|
367
|
+
this.element.removeAttribute(PAGINATION_PENDING_ATTRIBUTE)
|
|
301
368
|
if (this._loadMoreAbort) {
|
|
302
369
|
this._loadMoreAbort.abort()
|
|
303
370
|
this._loadMoreAbort = null
|
|
@@ -340,34 +407,76 @@ export default class extends Controller {
|
|
|
340
407
|
}
|
|
341
408
|
|
|
342
409
|
showEmptyState() {
|
|
343
|
-
|
|
344
|
-
|
|
410
|
+
// The placeholder comes from the server-rendered <template> rather than an
|
|
411
|
+
// HTML string on a data attribute: no innerHTML sink, and the button_to CSRF
|
|
412
|
+
// token in the "request permission" variant survives.
|
|
413
|
+
this.element.replaceChildren()
|
|
414
|
+
restoreTreeEmptyState(this.element)
|
|
415
|
+
this.markContentLoaded()
|
|
416
|
+
document.documentElement.classList.add('creative-alignment-ready')
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Distinct from showEmptyState(): used when the tree fetch itself fails
|
|
420
|
+
// (non-2xx, JSON parse failure, or a non-transient network error after
|
|
421
|
+
// retries are exhausted). The request never actually confirmed the tree is
|
|
422
|
+
// empty, so this must NOT render the Add/Import creation CTAs — a workspace
|
|
423
|
+
// that genuinely has creatives would otherwise look empty and invite
|
|
424
|
+
// duplicate creation.
|
|
425
|
+
showErrorState() {
|
|
426
|
+
this.element.replaceChildren()
|
|
427
|
+
if (this.hasErrorTextValue && this.errorTextValue) {
|
|
428
|
+
const message = document.createElement('p')
|
|
429
|
+
message.className = 'creative-tree-error'
|
|
430
|
+
message.style.textAlign = 'center'
|
|
431
|
+
message.textContent = this.errorTextValue
|
|
432
|
+
this.element.appendChild(message)
|
|
433
|
+
}
|
|
345
434
|
this.markContentLoaded()
|
|
346
435
|
document.documentElement.classList.add('creative-alignment-ready')
|
|
347
436
|
}
|
|
348
437
|
|
|
349
438
|
showLoadingIndicator() {
|
|
350
439
|
if (!this.loadingIndicator) {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
<span class="creative-loading-dot">.</span>
|
|
359
|
-
<span class="creative-loading-dot">.</span>
|
|
360
|
-
<span class="creative-loading-dot">.</span>
|
|
361
|
-
</span>
|
|
362
|
-
`
|
|
363
|
-
this.loadingIndicator = indicator
|
|
440
|
+
// The first load's placeholder is already in the DOM: index.html.erb renders
|
|
441
|
+
// it inside #creatives so the initial paint says "loading" rather than
|
|
442
|
+
// flashing the empty state until Stimulus boots. Adopt that node instead of
|
|
443
|
+
// building a replacement — swapping it out would blank the container for a
|
|
444
|
+
// frame, which is the flicker this whole arrangement exists to avoid.
|
|
445
|
+
this.loadingIndicator =
|
|
446
|
+
this.element.querySelector(':scope > [data-creatives-tree-loading]') || this.buildLoadingIndicator()
|
|
364
447
|
}
|
|
365
448
|
this.clearLoadedState()
|
|
366
|
-
|
|
367
|
-
|
|
449
|
+
// replaceChildren over `innerHTML = ''` + appendChild: the adopted placeholder
|
|
450
|
+
// is a child of the container, so wiping first would detach the very node
|
|
451
|
+
// being re-inserted.
|
|
452
|
+
this.element.replaceChildren(this.loadingIndicator)
|
|
368
453
|
this.startAnimation()
|
|
369
454
|
}
|
|
370
455
|
|
|
456
|
+
buildLoadingIndicator() {
|
|
457
|
+
const indicator = document.createElement('div')
|
|
458
|
+
indicator.className = 'creative-tree-loading-placeholder'
|
|
459
|
+
indicator.setAttribute('data-creatives-tree-loading', '')
|
|
460
|
+
indicator.setAttribute('role', 'status')
|
|
461
|
+
indicator.setAttribute('aria-live', 'polite')
|
|
462
|
+
indicator.setAttribute('aria-label', this.loadingLabel())
|
|
463
|
+
indicator.innerHTML = `
|
|
464
|
+
<span class="creative-loading-indicator" aria-hidden="true">
|
|
465
|
+
<span class="creative-loading-dot">.</span>
|
|
466
|
+
<span class="creative-loading-dot">.</span>
|
|
467
|
+
<span class="creative-loading-dot">.</span>
|
|
468
|
+
</span>
|
|
469
|
+
`
|
|
470
|
+
return indicator
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
loadingLabel() {
|
|
474
|
+
if (this.hasLoadingTextValue && this.loadingTextValue) return this.loadingTextValue
|
|
475
|
+
throw new Error(
|
|
476
|
+
'creatives--tree requires a translated loadingText value when no server loading placeholder is present',
|
|
477
|
+
)
|
|
478
|
+
}
|
|
479
|
+
|
|
371
480
|
hideLoadingIndicator() {
|
|
372
481
|
this.stopAnimation()
|
|
373
482
|
if (this.loadingIndicator && this.loadingIndicator.parentNode === this.element) {
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Controller } from '@hotwired/stimulus'
|
|
2
|
+
import csrfFetch from '../../lib/api/csrf_fetch'
|
|
3
|
+
import { alertDialog } from '../../lib/utils/dialog'
|
|
4
|
+
|
|
5
|
+
// Powers the "Request write access" CTA in the creative tree's empty state
|
|
6
|
+
// (see engines/collavre/app/views/collavre/creatives/_empty_state.html.erb).
|
|
7
|
+
//
|
|
8
|
+
// There is no backend concept of a "pending request" — Collavre::Concerns::Shareable#request_permission
|
|
9
|
+
// just fires a one-off inbox notification to the owner, it doesn't persist request
|
|
10
|
+
// state anywhere. So the "pending" pill here is a purely client-side, one-time
|
|
11
|
+
// confirmation that the click went through: it swaps in immediately after a
|
|
12
|
+
// successful POST and does NOT survive a page reload (a reload has no way to
|
|
13
|
+
// know a request is outstanding). That's an intentional, honest scope cut —
|
|
14
|
+
// see the PR description for the alternatives considered.
|
|
15
|
+
export default class extends Controller {
|
|
16
|
+
static targets = ['button', 'pending']
|
|
17
|
+
static values = { url: String, failureMessage: String }
|
|
18
|
+
|
|
19
|
+
async request(event) {
|
|
20
|
+
event.preventDefault()
|
|
21
|
+
if (!this.hasUrlValue || !this.hasButtonTarget) return
|
|
22
|
+
|
|
23
|
+
this.buttonTarget.disabled = true
|
|
24
|
+
try {
|
|
25
|
+
const response = await csrfFetch(this.urlValue, { method: 'POST' })
|
|
26
|
+
if (response.redirected) {
|
|
27
|
+
// fetch() follows redirects by default, so an expired session sends
|
|
28
|
+
// this POST to the sign-in page instead of the permission endpoint —
|
|
29
|
+
// the final response is a 200 for the login form, not a request
|
|
30
|
+
// confirmation. Send the browser to the page it actually landed on
|
|
31
|
+
// rather than claiming the request went through.
|
|
32
|
+
this.redirectToSignIn(response.url)
|
|
33
|
+
} else if (response.ok) {
|
|
34
|
+
this.showPending()
|
|
35
|
+
} else {
|
|
36
|
+
this.reportFailure()
|
|
37
|
+
}
|
|
38
|
+
} catch {
|
|
39
|
+
this.reportFailure()
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
reportFailure() {
|
|
44
|
+
this.buttonTarget.disabled = false
|
|
45
|
+
if (this.hasFailureMessageValue) alertDialog(this.failureMessageValue)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
showPending() {
|
|
49
|
+
this.buttonTarget.hidden = true
|
|
50
|
+
if (this.hasPendingTarget) {
|
|
51
|
+
this.pendingTarget.hidden = false
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
redirectToSignIn(url) {
|
|
56
|
+
window.location.href = url
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
import csrfFetch from "../lib/api/csrf_fetch"
|
|
3
|
+
|
|
4
|
+
export function setupNextUrl(nextUrlValue, adapters) {
|
|
5
|
+
const nextUrl = new URL(nextUrlValue, window.location.origin)
|
|
6
|
+
for (const adapter of adapters || []) nextUrl.searchParams.set(adapter, "1")
|
|
7
|
+
return `${nextUrl.pathname}${nextUrl.search}${nextUrl.hash}`
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// The browser handles consent and a short-lived registration grant only. The
|
|
11
|
+
// Tauri command keeps proxy secrets in Keychain and posts them directly to the
|
|
12
|
+
// local Rails sidecar, so no secret is exposed to the DOM.
|
|
13
|
+
export default class extends Controller {
|
|
14
|
+
static targets = ["consent", "submit", "error"]
|
|
15
|
+
static values = { tokenUrl: String, nextUrl: String, unavailable: String, failed: String }
|
|
16
|
+
|
|
17
|
+
toggle() {
|
|
18
|
+
this.submitTarget.disabled = !this.consentTarget.checked
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async install() {
|
|
22
|
+
if (!this.consentTarget.checked) {
|
|
23
|
+
this.submitTarget.disabled = true
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
this.errorTarget.hidden = true
|
|
28
|
+
this.submitTarget.disabled = true
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const invoke = window.__TAURI__?.core?.invoke
|
|
32
|
+
if (!invoke) throw new Error(this.unavailableValue)
|
|
33
|
+
|
|
34
|
+
const response = await csrfFetch(this.tokenUrlValue, { method: "POST" })
|
|
35
|
+
if (!response.ok) throw new Error(this.failedValue)
|
|
36
|
+
const { token } = await response.json()
|
|
37
|
+
const result = await invoke("desktop_proxy_complete_setup", {
|
|
38
|
+
registrationToken: token,
|
|
39
|
+
})
|
|
40
|
+
this.navigate(setupNextUrl(this.nextUrlValue, result.adapters))
|
|
41
|
+
} catch (error) {
|
|
42
|
+
this.errorTarget.textContent = error?.message || this.failedValue
|
|
43
|
+
this.errorTarget.hidden = false
|
|
44
|
+
this.submitTarget.disabled = !this.consentTarget.checked
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
navigate(url) {
|
|
49
|
+
window.location.assign(url)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
static targets = ["result"]
|
|
5
|
+
static values = { checking: String, success: String, failed: String }
|
|
6
|
+
|
|
7
|
+
async check(event) {
|
|
8
|
+
const button = event.currentTarget
|
|
9
|
+
const result = button.parentElement.querySelector("[data-gateway-check-target='result']")
|
|
10
|
+
button.disabled = true
|
|
11
|
+
result.textContent = this.checkingValue
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const response = await fetch(event.params.url, {
|
|
15
|
+
method: "POST",
|
|
16
|
+
headers: { "X-CSRF-Token": this.csrfToken, Accept: "application/json" }
|
|
17
|
+
})
|
|
18
|
+
const data = await response.json()
|
|
19
|
+
if (data.ok && data.identity_verified === false) {
|
|
20
|
+
result.textContent = `${this.successValue}: ${data.warning || ""}`
|
|
21
|
+
} else if (data.ok) {
|
|
22
|
+
result.textContent = `${this.successValue} (${(data.engines || []).join(", ")})`
|
|
23
|
+
} else {
|
|
24
|
+
result.textContent = `${this.failedValue}: ${data.error || ""}`
|
|
25
|
+
}
|
|
26
|
+
} catch (_error) {
|
|
27
|
+
result.textContent = this.failedValue
|
|
28
|
+
} finally {
|
|
29
|
+
button.disabled = false
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get csrfToken() {
|
|
34
|
+
return document.querySelector("meta[name='csrf-token']")?.content || ""
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -10,6 +10,7 @@ import CreativesExpansionController from "./creatives/expansion_controller"
|
|
|
10
10
|
import CreativesRowEditorController from "./creatives/row_editor_controller"
|
|
11
11
|
import CreativesTreeController from "./creatives/tree_controller"
|
|
12
12
|
import CreativesSyncController from "./creatives/sync_controller"
|
|
13
|
+
import CreativesWriteAccessRequestController from "./creatives/write_access_request_controller"
|
|
13
14
|
import CommentsListController from "./comments/list_controller"
|
|
14
15
|
import CommentsFormController from "./comments/form_controller"
|
|
15
16
|
import CommentsPresenceController from "./comments/presence_controller"
|
|
@@ -17,6 +18,8 @@ import CommentsMentionMenuController from "./comments/mention_menu_controller"
|
|
|
17
18
|
import CommentsTopicsController from "./comments/topics_controller"
|
|
18
19
|
import CommentsContextsController from "./comments/contexts_controller"
|
|
19
20
|
import CommentsDropTriggerController from "./comments/drop_trigger_controller"
|
|
21
|
+
import CommentsFeatureCardsController from "./comments/feature_cards_controller"
|
|
22
|
+
import CommentsPlaceholderController from "./comments/placeholder_controller"
|
|
20
23
|
import CommentsPopupController from "./comments/popup_controller"
|
|
21
24
|
import ClickTargetController from "./click_target_controller"
|
|
22
25
|
import TabsController from "./tabs_controller"
|
|
@@ -36,6 +39,12 @@ import ImageLightboxController from "./image_lightbox_controller"
|
|
|
36
39
|
import SearchPopupController from "./search_popup_controller"
|
|
37
40
|
import LandingVideoController from "./landing_video_controller"
|
|
38
41
|
import InboxBadgeController from "./inbox_badge_controller"
|
|
42
|
+
import LastVisitedCreativeController from "./last_visited_creative_controller"
|
|
43
|
+
import WorkspaceTreeController from "./workspace_tree_controller"
|
|
44
|
+
import GatewayCheckController from "./gateway_check_controller"
|
|
45
|
+
import AgentConnectionController from "./agent_connection_controller"
|
|
46
|
+
import AgentVendorController from "./agent_vendor_controller"
|
|
47
|
+
import DesktopProxySetupController from "./desktop_proxy_setup_controller"
|
|
39
48
|
|
|
40
49
|
// Export all controllers
|
|
41
50
|
export {
|
|
@@ -73,7 +82,13 @@ export {
|
|
|
73
82
|
SearchPopupController,
|
|
74
83
|
CommentBadgeController,
|
|
75
84
|
LandingVideoController,
|
|
76
|
-
InboxBadgeController
|
|
85
|
+
InboxBadgeController,
|
|
86
|
+
LastVisitedCreativeController,
|
|
87
|
+
WorkspaceTreeController,
|
|
88
|
+
GatewayCheckController,
|
|
89
|
+
AgentConnectionController,
|
|
90
|
+
AgentVendorController,
|
|
91
|
+
DesktopProxySetupController
|
|
77
92
|
}
|
|
78
93
|
|
|
79
94
|
// Registration function for use with a Stimulus application
|
|
@@ -88,6 +103,7 @@ export function registerControllers(application) {
|
|
|
88
103
|
application.register("creatives--row-editor", CreativesRowEditorController)
|
|
89
104
|
application.register("creatives--tree", CreativesTreeController)
|
|
90
105
|
application.register("creatives--sync", CreativesSyncController)
|
|
106
|
+
application.register("creatives--write-access-request", CreativesWriteAccessRequestController)
|
|
91
107
|
application.register("comments--list", CommentsListController)
|
|
92
108
|
application.register("comments--form", CommentsFormController)
|
|
93
109
|
application.register("comments--presence", CommentsPresenceController)
|
|
@@ -95,6 +111,8 @@ export function registerControllers(application) {
|
|
|
95
111
|
application.register("comments--topics", CommentsTopicsController)
|
|
96
112
|
application.register("comments--contexts", CommentsContextsController)
|
|
97
113
|
application.register("comments--drop-trigger", CommentsDropTriggerController)
|
|
114
|
+
application.register("comments--feature-cards", CommentsFeatureCardsController)
|
|
115
|
+
application.register("comments--placeholder", CommentsPlaceholderController)
|
|
98
116
|
application.register("comments--popup", CommentsPopupController)
|
|
99
117
|
application.register("click-target", ClickTargetController)
|
|
100
118
|
application.register("tabs", TabsController)
|
|
@@ -114,4 +132,10 @@ export function registerControllers(application) {
|
|
|
114
132
|
application.register("comment-badge", CommentBadgeController)
|
|
115
133
|
application.register("landing-video", LandingVideoController)
|
|
116
134
|
application.register("inbox-badge", InboxBadgeController)
|
|
135
|
+
application.register("last-visited-creative", LastVisitedCreativeController)
|
|
136
|
+
application.register("workspace-tree", WorkspaceTreeController)
|
|
137
|
+
application.register("gateway-check", GatewayCheckController)
|
|
138
|
+
application.register("agent-connection", AgentConnectionController)
|
|
139
|
+
application.register("agent-vendor", AgentVendorController)
|
|
140
|
+
application.register("desktop-proxy-setup", DesktopProxySetupController)
|
|
117
141
|
}
|