collavre 0.21.0 → 0.23.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.
Files changed (200) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -0
  3. data/app/assets/stylesheets/collavre/actiontext.css +251 -90
  4. data/app/assets/stylesheets/collavre/code_highlight.css +7 -201
  5. data/app/assets/stylesheets/collavre/comments_popup.css +169 -64
  6. data/app/assets/stylesheets/collavre/creatives.css +11 -2
  7. data/app/assets/stylesheets/collavre/modal_dialog.css +32 -0
  8. data/app/assets/stylesheets/collavre/popup.css +148 -0
  9. data/app/assets/stylesheets/collavre/tables.css +91 -0
  10. data/app/channels/collavre/agent_channel.rb +205 -0
  11. data/app/channels/collavre/inbox_badge_channel.rb +30 -0
  12. data/app/controllers/collavre/admin/integrations_controller.rb +16 -5
  13. data/app/controllers/collavre/api/v1/agents_controller.rb +777 -0
  14. data/app/controllers/collavre/api/v1/base_controller.rb +46 -0
  15. data/app/controllers/collavre/api/v1/mobile/agent_events_controller.rb +224 -0
  16. data/app/controllers/collavre/api/v1/mobile/base_controller.rb +95 -0
  17. data/app/controllers/collavre/api/v1/mobile/devices_controller.rb +31 -0
  18. data/app/controllers/collavre/api/v1/mobile/voice_commands_controller.rb +25 -0
  19. data/app/controllers/collavre/attachments_controller.rb +30 -2
  20. data/app/controllers/collavre/comments_controller.rb +1 -1
  21. data/app/controllers/collavre/creatives/attachments_controller.rb +79 -0
  22. data/app/controllers/collavre/creatives_controller.rb +107 -6
  23. data/app/controllers/collavre/tasks_controller.rb +21 -4
  24. data/app/controllers/collavre/topics_controller.rb +64 -1
  25. data/app/controllers/collavre/typo_corrections_controller.rb +39 -0
  26. data/app/controllers/concerns/collavre/comments/approval_actions.rb +57 -14
  27. data/app/controllers/concerns/collavre/users_controller/profile_and_settings.rb +16 -1
  28. data/app/controllers/concerns/collavre/users_controller/registration.rb +41 -1
  29. data/app/helpers/collavre/application_helper.rb +1 -0
  30. data/app/javascript/collavre.js +2 -0
  31. data/app/javascript/components/ImageResizer.jsx +9 -3
  32. data/app/javascript/components/InlineLexicalEditor.jsx +196 -96
  33. data/app/javascript/components/creative_tree_row.js +20 -3
  34. data/app/javascript/components/plugins/attachment_cleanup_plugin.jsx +3 -0
  35. data/app/javascript/components/plugins/image_upload_plugin.jsx +12 -0
  36. data/app/javascript/components/plugins/list_tab_indent_plugin.jsx +16 -0
  37. data/app/javascript/components/plugins/table_hover_actions_plugin.jsx +405 -0
  38. data/app/javascript/controllers/__tests__/inbox_badge_controller.test.js +73 -0
  39. data/app/javascript/controllers/__tests__/link_creative_controller.test.js +447 -0
  40. data/app/javascript/controllers/comment_controller.js +11 -5
  41. data/app/javascript/controllers/comment_version_controller.js +2 -1
  42. data/app/javascript/controllers/comments/__tests__/form_controller_double_submit.test.js +159 -0
  43. data/app/javascript/controllers/comments/__tests__/presence_controller.test.js +111 -2
  44. data/app/javascript/controllers/comments/__tests__/topics_controller_delete.test.js +94 -0
  45. data/app/javascript/controllers/comments/form_controller.js +21 -5
  46. data/app/javascript/controllers/comments/list_controller.js +35 -19
  47. data/app/javascript/controllers/comments/presence_controller.js +58 -6
  48. data/app/javascript/controllers/comments/topics_controller.js +14 -8
  49. data/app/javascript/controllers/creatives/__tests__/tree_controller.test.js +150 -0
  50. data/app/javascript/controllers/creatives/import_controller.js +2 -1
  51. data/app/javascript/controllers/creatives/select_mode_controller.js +2 -1
  52. data/app/javascript/controllers/creatives/tree_controller.js +142 -1
  53. data/app/javascript/controllers/image_lightbox_controller.js +2 -1
  54. data/app/javascript/controllers/inbox_badge_controller.js +33 -0
  55. data/app/javascript/controllers/index.js +4 -1
  56. data/app/javascript/controllers/link_creative_controller.js +451 -29
  57. data/app/javascript/controllers/share_modal_controller.js +4 -3
  58. data/app/javascript/controllers/topic_search_controller.js +2 -1
  59. data/app/javascript/creatives/drag_drop/event_handlers.js +14 -5
  60. data/app/javascript/creatives/topic_move_members_popup.js +156 -0
  61. data/app/javascript/creatives/tree_renderer.js +11 -0
  62. data/app/javascript/lib/__tests__/turbo_confirm.test.js +81 -0
  63. data/app/javascript/lib/__tests__/typo_correction.test.js +192 -0
  64. data/app/javascript/lib/api/__tests__/api_error.test.js +96 -0
  65. data/app/javascript/lib/api/__tests__/queue_manager.test.js +88 -1
  66. data/app/javascript/lib/api/api_error.js +108 -0
  67. data/app/javascript/lib/api/creatives.js +13 -0
  68. data/app/javascript/lib/api/queue_manager.js +38 -4
  69. data/app/javascript/lib/common_popup.js +18 -5
  70. data/app/javascript/lib/editor/__tests__/code_edit_view_token_parity.test.js +121 -0
  71. data/app/javascript/lib/editor/__tests__/code_language_roundtrip.test.js +152 -0
  72. data/app/javascript/lib/editor/__tests__/code_languages.test.js +93 -0
  73. data/app/javascript/lib/editor/code_languages.js +173 -0
  74. data/app/javascript/lib/editor/code_token_theme.js +41 -0
  75. data/app/javascript/lib/lexical/__tests__/color_import.test.js +318 -0
  76. data/app/javascript/lib/lexical/__tests__/image_focus.test.js +139 -0
  77. data/app/javascript/lib/lexical/__tests__/list_tab_indent.test.js +633 -0
  78. data/app/javascript/lib/lexical/__tests__/markdown_serialize.test.js +627 -0
  79. data/app/javascript/lib/lexical/__tests__/minimize_html.test.js +278 -0
  80. data/app/javascript/lib/lexical/__tests__/selection_boundary.test.js +88 -0
  81. data/app/javascript/lib/lexical/__tests__/table_transformer.test.js +163 -0
  82. data/app/javascript/lib/lexical/__tests__/trailing_paragraph.test.js +104 -0
  83. data/app/javascript/lib/lexical/color_import.js +186 -0
  84. data/app/javascript/lib/lexical/list_tab_indent.js +210 -0
  85. data/app/javascript/lib/lexical/markdown_serialize.js +320 -0
  86. data/app/javascript/lib/lexical/minimize_html.js +182 -0
  87. data/app/javascript/lib/lexical/selection_boundary.js +58 -0
  88. data/app/javascript/lib/lexical/table_transformer.js +182 -0
  89. data/app/javascript/lib/lexical/trailing_paragraph.js +29 -0
  90. data/app/javascript/lib/lexical/video_node.jsx +96 -0
  91. data/app/javascript/lib/turbo_confirm.js +46 -0
  92. data/app/javascript/lib/typo_correction.js +146 -0
  93. data/app/javascript/lib/utils/__tests__/confirm_dialog.test.js +88 -0
  94. data/app/javascript/lib/utils/__tests__/dialog.test.js +92 -0
  95. data/app/javascript/lib/utils/__tests__/markdown.test.js +153 -0
  96. data/app/javascript/lib/utils/__tests__/sanitize_description.test.js +68 -0
  97. data/app/javascript/lib/utils/__tests__/table_download.test.js +93 -0
  98. data/app/javascript/lib/utils/confirm_dialog.js +10 -0
  99. data/app/javascript/lib/utils/dialog.js +300 -0
  100. data/app/javascript/lib/utils/markdown.js +154 -67
  101. data/app/javascript/lib/utils/sanitize_description.js +31 -0
  102. data/app/javascript/lib/utils/table_download.js +15 -0
  103. data/app/javascript/modules/__tests__/typo_corrector.test.js +365 -0
  104. data/app/javascript/modules/creative_row_editor.js +110 -70
  105. data/app/javascript/modules/export_to_markdown.js +2 -1
  106. data/app/javascript/modules/lexical_inline_editor.jsx +2 -1
  107. data/app/javascript/modules/slide_view.js +11 -2
  108. data/app/javascript/modules/typo_corrector.js +534 -0
  109. data/app/jobs/collavre/ai_agent_job.rb +92 -3
  110. data/app/jobs/collavre/cancel_offline_delegated_tasks_job.rb +134 -0
  111. data/app/jobs/collavre/claude_channel_presence_job.rb +91 -0
  112. data/app/jobs/collavre/compress_job.rb +6 -2
  113. data/app/models/collavre/agent_subscription.rb +52 -0
  114. data/app/models/collavre/comment/broadcastable.rb +46 -7
  115. data/app/models/collavre/comment/claude_channel_permission.rb +145 -0
  116. data/app/models/collavre/comment/notifiable.rb +14 -4
  117. data/app/models/collavre/comment.rb +124 -11
  118. data/app/models/collavre/creative/describable.rb +220 -4
  119. data/app/models/collavre/creative_share.rb +1 -0
  120. data/app/models/collavre/task.rb +49 -5
  121. data/app/models/collavre/topic.rb +5 -0
  122. data/app/models/collavre/user.rb +61 -1
  123. data/app/services/collavre/agent_session_abort.rb +28 -0
  124. data/app/services/collavre/ai_agent/claude_channel_adapter.rb +79 -0
  125. data/app/services/collavre/ai_agent/response_finalizer.rb +4 -2
  126. data/app/services/collavre/ai_agent_service.rb +68 -49
  127. data/app/services/collavre/ai_client.rb +28 -10
  128. data/app/services/collavre/attachment_backfill.rb +26 -0
  129. data/app/services/collavre/auto_theme_generator.rb +1 -1
  130. data/app/services/collavre/creatives/breadcrumb_resolver.rb +91 -0
  131. data/app/services/collavre/creatives/filter_pipeline.rb +26 -42
  132. data/app/services/collavre/creatives/filters/search_filter.rb +12 -2
  133. data/app/services/collavre/creatives/index_query.rb +195 -24
  134. data/app/services/collavre/creatives/permission_filter.rb +50 -0
  135. data/app/services/collavre/creatives/reveal_path_resolver.rb +118 -0
  136. data/app/services/collavre/creatives/tree_builder.rb +2 -1
  137. data/app/services/collavre/crons/recurring_task_arguments.rb +28 -0
  138. data/app/services/collavre/gemini_parent_recommender.rb +1 -1
  139. data/app/services/collavre/inbox_reply_service.rb +5 -0
  140. data/app/services/collavre/markdown_converter.rb +13 -3
  141. data/app/services/collavre/mobile/event_summarizer.rb +40 -0
  142. data/app/services/collavre/orchestration/agent_orchestrator.rb +33 -7
  143. data/app/services/collavre/orchestration/arbiter.rb +16 -0
  144. data/app/services/collavre/orchestration/matcher.rb +79 -4
  145. data/app/services/collavre/orchestration/policy_resolver.rb +4 -3
  146. data/app/services/collavre/orchestration/stuck_detector.rb +146 -19
  147. data/app/services/collavre/tools/creative_attach_files_service.rb +29 -63
  148. data/app/services/collavre/tools/creative_batch_service.rb +3 -2
  149. data/app/services/collavre/tools/creative_create_service.rb +8 -8
  150. data/app/services/collavre/tools/creative_remove_attachment_service.rb +7 -5
  151. data/app/services/collavre/tools/creative_update_service.rb +23 -8
  152. data/app/services/collavre/tools/cron_list_service.rb +1 -14
  153. data/app/services/collavre/topics/orphaned_cron_notifier.rb +68 -0
  154. data/app/services/collavre/typo_corrector.rb +188 -0
  155. data/app/views/collavre/admin/integrations/_category.html.erb +1 -1
  156. data/app/views/collavre/admin/integrations/_setting_row.html.erb +27 -11
  157. data/app/views/collavre/comments/_comment.html.erb +15 -1
  158. data/app/views/collavre/comments/_comments_popup.html.erb +18 -3
  159. data/app/views/collavre/creatives/_inline_edit_form.html.erb +1 -0
  160. data/app/views/collavre/creatives/_topic_move_members_modal.html.erb +42 -0
  161. data/app/views/collavre/creatives/index.html.erb +14 -1
  162. data/app/views/collavre/creatives/slide_view.html.erb +1 -1
  163. data/app/views/collavre/shared/_link_creative_modal.html.erb +6 -2
  164. data/app/views/collavre/users/show.html.erb +3 -0
  165. data/app/views/collavre/users/typo_correction.html.erb +50 -0
  166. data/app/views/layouts/collavre/slide.html.erb +1 -0
  167. data/config/locales/channels.en.yml +2 -0
  168. data/config/locales/channels.ko.yml +2 -0
  169. data/config/locales/claude_channel.en.yml +16 -0
  170. data/config/locales/claude_channel.ko.yml +16 -0
  171. data/config/locales/comments.en.yml +18 -0
  172. data/config/locales/comments.ko.yml +18 -0
  173. data/config/locales/creatives.en.yml +2 -0
  174. data/config/locales/creatives.ko.yml +2 -0
  175. data/config/locales/integrations.en.yml +13 -2
  176. data/config/locales/integrations.ko.yml +13 -2
  177. data/config/locales/mobile.en.yml +16 -0
  178. data/config/locales/mobile.ko.yml +16 -0
  179. data/config/locales/orchestration.en.yml +1 -0
  180. data/config/locales/orchestration.ko.yml +1 -0
  181. data/config/locales/users.en.yml +15 -0
  182. data/config/locales/users.ko.yml +15 -0
  183. data/config/routes.rb +25 -0
  184. data/db/migrate/20260609000000_drop_action_text_rich_texts.rb +20 -0
  185. data/db/migrate/20260609005000_add_session_id_to_topics.rb +16 -0
  186. data/db/migrate/20260609010000_create_agent_subscriptions.rb +19 -0
  187. data/db/migrate/20260609020000_add_last_seen_at_to_agent_subscriptions.rb +23 -0
  188. data/db/migrate/20260609030000_add_session_id_to_agent_subscriptions.rb +17 -0
  189. data/db/migrate/20260609190659_backfill_creative_files_into_description.rb +24 -0
  190. data/db/migrate/20260612000000_add_topic_concurrency_defer_to_comments.rb +38 -0
  191. data/db/migrate/20260617090000_add_typo_correction_settings_to_users.rb +18 -0
  192. data/db/seeds.rb +51 -0
  193. data/lib/collavre/engine.rb +0 -1
  194. data/lib/collavre/integration_settings/key_definition.rb +6 -0
  195. data/lib/collavre/integration_settings/registry.rb +7 -2
  196. data/lib/collavre/version.rb +1 -1
  197. data/lib/generators/collavre/install/install_generator.rb +1 -0
  198. metadata +85 -3
  199. data/app/services/collavre/openclaw_abort_service.rb +0 -45
  200. data/app/services/collavre/tools/description_normalizable.rb +0 -16
@@ -328,6 +328,13 @@ body.chat-fullscreen {
328
328
  margin-top: 0.2em;
329
329
  }
330
330
 
331
+ .comment-content img,
332
+ .comment-content video {
333
+ max-width: 100%;
334
+ height: auto;
335
+ border-radius: var(--radius-2);
336
+ }
337
+
331
338
  .comment-content pre {
332
339
  white-space: pre-wrap;
333
340
  word-break: break-word;
@@ -360,67 +367,8 @@ body.chat-fullscreen {
360
367
  margin: 0;
361
368
  }
362
369
 
363
- /* Table styling */
364
- .comment-content table {
365
- border-collapse: collapse;
366
- width: 100%;
367
- font-size: 0.85em;
368
- margin: 0;
369
- }
370
-
371
- .comment-content table th,
372
- .comment-content table td {
373
- border: 1px solid var(--color-border);
374
- padding: 0.3em 0.6em;
375
- text-align: left;
376
- white-space: nowrap;
377
- }
378
-
379
- .comment-content table th {
380
- background: var(--color-section-bg);
381
- font-weight: 600;
382
- }
383
-
384
- .comment-content table tr:hover td {
385
- background: color-mix(in srgb, var(--color-section-bg) 40%, transparent);
386
- }
387
-
388
- /* Table download wrapper */
389
- .table-download-wrapper {
390
- position: relative;
391
- overflow-x: auto;
392
- margin: 0.4em 0;
393
- }
394
-
395
- .table-download-toolbar {
396
- display: flex;
397
- justify-content: flex-end;
398
- gap: 0.3em;
399
- padding: 0.15em 0;
400
- opacity: 0;
401
- transition: opacity 0.15s ease;
402
- }
403
-
404
- .table-download-wrapper:hover .table-download-toolbar {
405
- opacity: 1;
406
- }
407
-
408
- .table-download-btn {
409
- background: none;
410
- border: 1px solid var(--color-border);
411
- border-radius: var(--radius-2);
412
- color: var(--color-muted);
413
- font-size: 0.75em;
414
- padding: 0.15em 0.5em;
415
- cursor: pointer;
416
- line-height: 1.4;
417
- transition: color 0.15s ease, border-color 0.15s ease;
418
- }
419
-
420
- .table-download-btn:hover {
421
- color: var(--color-active);
422
- border-color: var(--color-active);
423
- }
370
+ /* Table + download-toolbar styling moved to collavre/tables.css (shared with
371
+ creative description tables). Load 'collavre/tables' alongside this file. */
424
372
 
425
373
  /* Quote indicator in form */
426
374
  .comment-quote-indicator {
@@ -806,7 +754,8 @@ body.chat-fullscreen {
806
754
  }
807
755
 
808
756
  .edit-comment-action-btn,
809
- .approve-comment-btn {
757
+ .approve-comment-btn,
758
+ .deny-comment-btn {
810
759
  padding: 0.15em 0.4em;
811
760
  border-radius: var(--radius-2);
812
761
  font-size: 0.75rem;
@@ -836,6 +785,18 @@ body.chat-fullscreen {
836
785
  border-color: var(--color-success);
837
786
  }
838
787
 
788
+ .deny-comment-btn {
789
+ border: 1px solid color-mix(in srgb, var(--color-danger) 35%, transparent);
790
+ background: color-mix(in srgb, var(--color-danger) 15%, transparent);
791
+ color: var(--color-danger);
792
+ }
793
+
794
+ .deny-comment-btn:hover {
795
+ background: var(--color-danger);
796
+ color: var(--text-on-badge);
797
+ border-color: var(--color-danger);
798
+ }
799
+
839
800
  .comment-status-label {
840
801
  margin-left: 0.4em;
841
802
  font-size: 0.75em;
@@ -858,6 +819,11 @@ body.chat-fullscreen {
858
819
  color: var(--color-complete);
859
820
  }
860
821
 
822
+ .comment-status-label.denied-label {
823
+ background-color: color-mix(in srgb, var(--color-danger) 20%, transparent);
824
+ color: var(--color-danger);
825
+ }
826
+
861
827
  .comment-status-label.pending-label {
862
828
  background-color: color-mix(in srgb, var(--color-warning) 20%, transparent);
863
829
  color: var(--color-warning);
@@ -1111,6 +1077,34 @@ body.chat-fullscreen {
1111
1077
  gap: var(--space-1);
1112
1078
  }
1113
1079
 
1080
+ /* Horizontally-scrollable viewport for channel chips + typing indicator.
1081
+ Keeps PR/Preview badges and live typing on a single line so they never
1082
+ wrap and push the form down. The scroll-prev button stays pinned at the
1083
+ left because it is a flex sibling of (not inside) this viewport.
1084
+ min-width:0 lets this flex child shrink below its content width, which is
1085
+ what actually enables overflow scrolling. */
1086
+ #typing-scroll-viewport {
1087
+ flex: 1;
1088
+ min-width: 0;
1089
+ display: flex;
1090
+ align-items: center;
1091
+ gap: var(--space-1);
1092
+ flex-wrap: nowrap;
1093
+ overflow-x: auto;
1094
+ overflow-y: hidden;
1095
+ scrollbar-width: thin;
1096
+ scroll-behavior: smooth;
1097
+ }
1098
+
1099
+ #typing-scroll-viewport::-webkit-scrollbar {
1100
+ height: 4px;
1101
+ }
1102
+
1103
+ #typing-scroll-viewport::-webkit-scrollbar-thumb {
1104
+ background: var(--border-color);
1105
+ border-radius: var(--radius-2);
1106
+ }
1107
+
1114
1108
  .scroll-prev-msg-btn {
1115
1109
  background: none;
1116
1110
  border: 1px solid var(--border-color);
@@ -1131,10 +1125,11 @@ body.chat-fullscreen {
1131
1125
 
1132
1126
  #typing-indicator {
1133
1127
  font-style: italic;
1134
- flex: 1;
1128
+ flex: 0 0 auto;
1135
1129
  min-height: var(--space-px-5);
1136
1130
  display: flex;
1137
1131
  align-items: center;
1132
+ white-space: nowrap;
1138
1133
  }
1139
1134
 
1140
1135
  #typing-indicator .avatar-wrapper {
@@ -1188,7 +1183,8 @@ body.chat-fullscreen {
1188
1183
  #channel-chips-container,
1189
1184
  .channel-chips {
1190
1185
  display: inline-flex;
1191
- flex-wrap: wrap;
1186
+ flex-wrap: nowrap;
1187
+ flex-shrink: 0;
1192
1188
  gap: var(--space-1);
1193
1189
  align-items: center;
1194
1190
  }
@@ -1941,3 +1937,112 @@ body.chat-fullscreen {
1941
1937
  li:hover .topic-create-option {
1942
1938
  text-decoration: underline;
1943
1939
  }
1940
+
1941
+ /* ── Inline typo correction (volatile overlay; never serialized) ───────────── */
1942
+ /* The backdrop mirrors the textarea text exactly and paints <mark> spans over
1943
+ it. The backdrop sits ON TOP of the textarea (higher z-index) but is
1944
+ pointer-events:none, so clicks pass through to the textarea everywhere EXCEPT
1945
+ over a <mark> (pointer-events:auto) — that's what makes the marks clickable.
1946
+ If the textarea were on top, the browser would hit-test it first and the mark
1947
+ click handlers would never fire. The backdrop's text is transparent, so the
1948
+ textarea's real (opaque) glyphs show through and only the underlines paint. */
1949
+ .typo-input-wrap {
1950
+ position: relative;
1951
+ display: block;
1952
+ }
1953
+
1954
+ .typo-input-wrap > textarea {
1955
+ position: relative;
1956
+ background: transparent;
1957
+ z-index: 0;
1958
+ }
1959
+
1960
+ .typo-backdrop {
1961
+ position: absolute;
1962
+ inset: 0;
1963
+ overflow: hidden;
1964
+ pointer-events: none;
1965
+ z-index: 1;
1966
+ }
1967
+
1968
+ .typo-highlights {
1969
+ margin: 0;
1970
+ color: transparent;
1971
+ white-space: pre-wrap;
1972
+ overflow-wrap: break-word;
1973
+ word-break: break-word;
1974
+ }
1975
+
1976
+ /* Two states, distinguished by BOTH shape and colour (colour-blind safe). */
1977
+ .typo-mark {
1978
+ background: transparent;
1979
+ color: transparent;
1980
+ pointer-events: auto;
1981
+ cursor: pointer;
1982
+ border-radius: 2px;
1983
+ }
1984
+
1985
+ /* Auto-applied (>= threshold): faint straight underline — resolved, dim. */
1986
+ .typo-mark-applied {
1987
+ text-decoration: underline solid var(--color-link);
1988
+ text-decoration-thickness: 1px;
1989
+ text-underline-offset: 2px;
1990
+ opacity: 0.55;
1991
+ }
1992
+
1993
+ /* Candidate (< threshold): wavy dotted amber underline — needs a decision. */
1994
+ .typo-mark-candidate {
1995
+ text-decoration: underline wavy var(--color-warning);
1996
+ text-decoration-thickness: 1px;
1997
+ text-underline-offset: 2px;
1998
+ }
1999
+
2000
+ /* Creatable combobox reusing CommonPopup positioning. */
2001
+ .typo-popup {
2002
+ position: absolute;
2003
+ z-index: 100000;
2004
+ min-width: 180px;
2005
+ max-width: 320px;
2006
+ background: var(--surface-section);
2007
+ border: 1px solid var(--border-color);
2008
+ border-radius: 8px;
2009
+ box-shadow: 0 6px 24px rgba(0, 0, 0, 0.18);
2010
+ padding: 6px;
2011
+ }
2012
+
2013
+ .typo-popup-input {
2014
+ width: 100%;
2015
+ box-sizing: border-box;
2016
+ padding: 6px 8px;
2017
+ border: 1px solid var(--border-color);
2018
+ border-radius: 6px;
2019
+ font: inherit;
2020
+ margin-bottom: 6px;
2021
+ }
2022
+
2023
+ .typo-popup-list {
2024
+ list-style: none;
2025
+ margin: 0;
2026
+ padding: 0;
2027
+ max-height: 200px;
2028
+ overflow-y: auto;
2029
+ }
2030
+
2031
+ .typo-popup-list .common-popup-item {
2032
+ padding: 6px 8px;
2033
+ border-radius: 6px;
2034
+ cursor: pointer;
2035
+ display: flex;
2036
+ justify-content: space-between;
2037
+ gap: 8px;
2038
+ }
2039
+
2040
+ .typo-popup-list .common-popup-item.active,
2041
+ .typo-popup-list .common-popup-item:hover {
2042
+ background: var(--surface-hover);
2043
+ }
2044
+
2045
+ .typo-popup-role {
2046
+ color: var(--text-muted);
2047
+ font-size: 0.85em;
2048
+ }
@@ -257,14 +257,23 @@ creative-tree-row.show-edit .creative-row {
257
257
  word-break: break-word;
258
258
  }
259
259
 
260
- /* Allow attachments and images to be clickable independently of the parent link */
260
+ /* Allow attachments, images, and videos to be interactive independently of the
261
+ parent link. Without pointer-events on the video, the clickable creative row
262
+ would swallow clicks on the native playback controls (play/pause/fullscreen). */
261
263
  .creative-content a[download],
262
- .creative-content img {
264
+ .creative-content img,
265
+ .creative-content video {
263
266
  pointer-events: auto;
264
267
  position: relative;
265
268
  z-index: var(--layer-1);
266
269
  }
267
270
 
271
+ .creative-content video {
272
+ max-width: 100%;
273
+ height: auto;
274
+ border-radius: var(--radius-2);
275
+ }
276
+
268
277
  .creative-row:hover .creative-content {
269
278
  cursor: pointer;
270
279
  }
@@ -32,6 +32,17 @@
32
32
  to { opacity: 1; }
33
33
  }
34
34
 
35
+ /* Top-layer dialogs (alert/confirm/prompt rendered as <dialog>.showModal())
36
+ * use the native ::backdrop instead of the .modal-dialog-overlay div, so the
37
+ * backdrop covers everything below — including other top-layer surfaces such
38
+ * as the image lightbox. Only matches real <dialog> elements; the <div>
39
+ * consumers of .modal-dialog never get a ::backdrop. */
40
+ dialog.modal-dialog::backdrop {
41
+ background: rgba(0, 0, 0, 0.5);
42
+ backdrop-filter: blur(4px);
43
+ -webkit-backdrop-filter: blur(4px);
44
+ }
45
+
35
46
  /* ── Panel ── */
36
47
  .modal-dialog {
37
48
  display: none;
@@ -180,6 +191,27 @@ textarea.modal-dialog-input {
180
191
  opacity: 0.9;
181
192
  }
182
193
 
194
+ /* Destructive confirm action (delete/unlink). Matches .btn-danger convention. */
195
+ .modal-dialog-btn-danger {
196
+ background: var(--color-danger);
197
+ color: var(--text-on-badge);
198
+ border-color: var(--color-danger);
199
+ }
200
+
201
+ .modal-dialog-btn-danger:hover {
202
+ opacity: 0.9;
203
+ }
204
+
205
+ /* ── Confirm dialog message ──
206
+ * pre-wrap preserves the \n line breaks that native confirm() honored. */
207
+ .confirm-dialog-message {
208
+ white-space: pre-wrap;
209
+ overflow-wrap: break-word;
210
+ color: var(--text-primary);
211
+ font-size: var(--text-1);
212
+ line-height: var(--leading-3, 1.5);
213
+ }
214
+
183
215
  /* ── Footer hints (keyboard shortcuts) ── */
184
216
  .modal-dialog-hints {
185
217
  display: flex;
@@ -396,3 +396,151 @@ a.popup-menu-item:hover {
396
396
  min-width: 100%;
397
397
  }
398
398
  }
399
+
400
+ /* ── Link-creative picker: mini-tree + flat search results ── */
401
+ .link-creative-list {
402
+ list-style: none;
403
+ margin: 0;
404
+ padding: 0;
405
+ max-height: 320px;
406
+ overflow-y: auto;
407
+ }
408
+
409
+ /* Tree (browse) rows */
410
+ .link-tree-item {
411
+ list-style: none;
412
+ }
413
+
414
+ .link-tree-children {
415
+ list-style: none;
416
+ margin: 0;
417
+ padding: 0;
418
+ }
419
+
420
+ .link-tree-row {
421
+ display: flex;
422
+ align-items: center;
423
+ gap: 0.35em;
424
+ padding: 0.3em 0.5em;
425
+ border-radius: var(--radius-1);
426
+ cursor: pointer;
427
+ }
428
+
429
+ .link-tree-row:hover,
430
+ .link-tree-row.active {
431
+ background: var(--surface-hover);
432
+ }
433
+
434
+ .link-tree-toggle {
435
+ flex: 0 0 auto;
436
+ width: 1.75em;
437
+ height: 1.75em;
438
+ display: inline-flex;
439
+ align-items: center;
440
+ justify-content: center;
441
+ background: transparent;
442
+ border: none;
443
+ border-radius: var(--radius-1);
444
+ padding: 0;
445
+ line-height: 1;
446
+ color: var(--text-secondary);
447
+ cursor: pointer;
448
+ appearance: none;
449
+ }
450
+
451
+ .link-tree-toggle svg {
452
+ display: block;
453
+ width: 16px;
454
+ height: 16px;
455
+ }
456
+
457
+ .link-tree-toggle:hover {
458
+ color: var(--text-primary);
459
+ background: var(--surface-hover);
460
+ }
461
+
462
+ .link-tree-toggle-empty {
463
+ visibility: hidden;
464
+ cursor: default;
465
+ }
466
+
467
+ .link-tree-label {
468
+ flex: 1 1 auto;
469
+ min-width: 0;
470
+ overflow: hidden;
471
+ text-overflow: ellipsis;
472
+ white-space: nowrap;
473
+ color: var(--text-primary);
474
+ }
475
+
476
+ .link-tree-loading,
477
+ .link-tree-empty,
478
+ .link-popup-message {
479
+ list-style: none;
480
+ padding: 0.35em 0.6em;
481
+ color: var(--text-muted);
482
+ font-size: var(--text-0);
483
+ }
484
+
485
+ /* Flat search results with breadcrumb */
486
+ .link-result-item {
487
+ list-style: none;
488
+ padding: 0.4em 0.5em;
489
+ border-radius: var(--radius-1);
490
+ cursor: pointer;
491
+ }
492
+
493
+ .link-result-item:hover,
494
+ .link-result-item.active {
495
+ background: var(--surface-hover);
496
+ }
497
+
498
+ .link-result-label {
499
+ color: var(--text-primary);
500
+ overflow: hidden;
501
+ text-overflow: ellipsis;
502
+ white-space: nowrap;
503
+ }
504
+
505
+ .link-result-path {
506
+ display: flex;
507
+ flex-wrap: wrap;
508
+ align-items: center;
509
+ gap: 0.15em;
510
+ margin-top: 0.15em;
511
+ font-size: var(--text-00);
512
+ color: var(--text-muted);
513
+ }
514
+
515
+ .link-crumb {
516
+ background: transparent;
517
+ border: none;
518
+ padding: 0 0.1em;
519
+ font-size: inherit;
520
+ color: var(--text-muted);
521
+ cursor: pointer;
522
+ appearance: none;
523
+ max-width: 14ch;
524
+ overflow: hidden;
525
+ text-overflow: ellipsis;
526
+ white-space: nowrap;
527
+ }
528
+
529
+ .link-crumb:hover {
530
+ color: var(--color-link);
531
+ text-decoration: underline;
532
+ }
533
+
534
+ .link-crumb-sep {
535
+ color: var(--text-muted);
536
+ opacity: 0.6;
537
+ }
538
+
539
+ .link-crumb-restricted {
540
+ cursor: default;
541
+ opacity: 0.6;
542
+ }
543
+ .link-crumb-restricted:hover {
544
+ color: var(--text-muted);
545
+ text-decoration: none;
546
+ }
@@ -0,0 +1,91 @@
1
+ /*
2
+ * Shared markdown table styling + CSV/Excel download toolbar.
3
+ *
4
+ * Single source of truth so chat-message tables (.comment-content) and creative
5
+ * description tables (.creative-content / .creative-title-content) render
6
+ * identically and both expose the same top-right download buttons. The download
7
+ * toolbar markup is produced by lib/utils/table_download.js.
8
+ *
9
+ * Load this wherever either context renders: the creatives index, the slide
10
+ * layout, and any page that shows the comments popup.
11
+ */
12
+
13
+ /* Table styling */
14
+ .comment-content table,
15
+ .creative-content table,
16
+ .creative-title-content table {
17
+ border-collapse: collapse;
18
+ width: 100%;
19
+ font-size: 0.85em;
20
+ margin: 0;
21
+ /* Override legacy actiontext.css fixed layout so creative tables expand to
22
+ content and horizontally scroll like chat tables (tables.css loads after
23
+ actiontext.css; same specificity, so it must restate this property). */
24
+ table-layout: auto;
25
+ }
26
+
27
+ .comment-content table th,
28
+ .comment-content table td,
29
+ .creative-content table th,
30
+ .creative-content table td,
31
+ .creative-title-content table th,
32
+ .creative-title-content table td {
33
+ border: 1px solid var(--color-border);
34
+ padding: 0.3em 0.6em;
35
+ text-align: left;
36
+ white-space: nowrap;
37
+ /* Clear actiontext.css cell floors so creative cells size to content like
38
+ chat cells instead of forcing a min width / breaking long words. */
39
+ min-width: 0;
40
+ word-break: normal;
41
+ }
42
+
43
+ .comment-content table th,
44
+ .creative-content table th,
45
+ .creative-title-content table th {
46
+ background: var(--color-section-bg);
47
+ font-weight: 600;
48
+ }
49
+
50
+ .comment-content table tr:hover td,
51
+ .creative-content table tr:hover td,
52
+ .creative-title-content table tr:hover td {
53
+ background: color-mix(in srgb, var(--color-section-bg) 40%, transparent);
54
+ }
55
+
56
+ /* Table download wrapper */
57
+ .table-download-wrapper {
58
+ position: relative;
59
+ overflow-x: auto;
60
+ margin: 0.4em 0;
61
+ }
62
+
63
+ .table-download-toolbar {
64
+ display: flex;
65
+ justify-content: flex-end;
66
+ gap: 0.3em;
67
+ padding: 0.15em 0;
68
+ opacity: 0;
69
+ transition: opacity 0.15s ease;
70
+ }
71
+
72
+ .table-download-wrapper:hover .table-download-toolbar {
73
+ opacity: 1;
74
+ }
75
+
76
+ .table-download-btn {
77
+ background: none;
78
+ border: 1px solid var(--color-border);
79
+ border-radius: var(--radius-2);
80
+ color: var(--color-muted);
81
+ font-size: 0.75em;
82
+ padding: 0.15em 0.5em;
83
+ cursor: pointer;
84
+ line-height: 1.4;
85
+ transition: color 0.15s ease, border-color 0.15s ease;
86
+ }
87
+
88
+ .table-download-btn:hover {
89
+ color: var(--color-active);
90
+ border-color: var(--color-active);
91
+ }