@things-factory/board-ai 10.0.0-beta.108

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 (329) hide show
  1. package/PLAN-styling-tools.md +1253 -0
  2. package/client/components/board-ai-chat.test.ts +149 -0
  3. package/client/components/board-ai-chat.ts +2574 -0
  4. package/client/components/chat-echo-dedup.test.ts +116 -0
  5. package/client/components/chat-echo-dedup.ts +89 -0
  6. package/client/components/chat-input-builder.ts +47 -0
  7. package/client/components/default-catalog-entries.test.ts +59 -0
  8. package/client/components/default-catalog-entries.ts +100 -0
  9. package/client/components/default-slash-templates.test.ts +55 -0
  10. package/client/components/default-slash-templates.ts +129 -0
  11. package/client/components/markdown.test.ts +518 -0
  12. package/client/components/markdown.ts +508 -0
  13. package/client/components/mention-popup-helpers.ts +433 -0
  14. package/client/components/mention-popup.test.ts +588 -0
  15. package/client/components/mention-popup.ts +238 -0
  16. package/client/index.ts +11 -0
  17. package/client/tsconfig.json +13 -0
  18. package/client/utils/board-edit-patch.ts +374 -0
  19. package/config/config.development.js +43 -0
  20. package/config/config.production.js +15 -0
  21. package/dist-client/client/components/board-ai-chat.d.ts +277 -0
  22. package/dist-client/client/components/board-ai-chat.js +2444 -0
  23. package/dist-client/client/components/board-ai-chat.js.map +1 -0
  24. package/dist-client/client/components/board-ai-chat.test.d.ts +1 -0
  25. package/dist-client/client/components/board-ai-chat.test.js +138 -0
  26. package/dist-client/client/components/board-ai-chat.test.js.map +1 -0
  27. package/dist-client/client/components/chat-echo-dedup.d.ts +45 -0
  28. package/dist-client/client/components/chat-echo-dedup.js +58 -0
  29. package/dist-client/client/components/chat-echo-dedup.js.map +1 -0
  30. package/dist-client/client/components/chat-echo-dedup.test.d.ts +1 -0
  31. package/dist-client/client/components/chat-echo-dedup.test.js +101 -0
  32. package/dist-client/client/components/chat-echo-dedup.test.js.map +1 -0
  33. package/dist-client/client/components/chat-input-builder.d.ts +39 -0
  34. package/dist-client/client/components/chat-input-builder.js +26 -0
  35. package/dist-client/client/components/chat-input-builder.js.map +1 -0
  36. package/dist-client/client/components/default-catalog-entries.d.ts +15 -0
  37. package/dist-client/client/components/default-catalog-entries.js +86 -0
  38. package/dist-client/client/components/default-catalog-entries.js.map +1 -0
  39. package/dist-client/client/components/default-catalog-entries.test.d.ts +1 -0
  40. package/dist-client/client/components/default-catalog-entries.test.js +52 -0
  41. package/dist-client/client/components/default-catalog-entries.test.js.map +1 -0
  42. package/dist-client/client/components/default-slash-templates.d.ts +17 -0
  43. package/dist-client/client/components/default-slash-templates.js +113 -0
  44. package/dist-client/client/components/default-slash-templates.js.map +1 -0
  45. package/dist-client/client/components/default-slash-templates.test.d.ts +1 -0
  46. package/dist-client/client/components/default-slash-templates.test.js +51 -0
  47. package/dist-client/client/components/default-slash-templates.test.js.map +1 -0
  48. package/dist-client/client/components/markdown.d.ts +60 -0
  49. package/dist-client/client/components/markdown.js +422 -0
  50. package/dist-client/client/components/markdown.js.map +1 -0
  51. package/dist-client/client/components/markdown.test.d.ts +1 -0
  52. package/dist-client/client/components/markdown.test.js +418 -0
  53. package/dist-client/client/components/markdown.test.js.map +1 -0
  54. package/dist-client/client/components/mention-popup-helpers.d.ts +165 -0
  55. package/dist-client/client/components/mention-popup-helpers.js +330 -0
  56. package/dist-client/client/components/mention-popup-helpers.js.map +1 -0
  57. package/dist-client/client/components/mention-popup.d.ts +35 -0
  58. package/dist-client/client/components/mention-popup.js +223 -0
  59. package/dist-client/client/components/mention-popup.js.map +1 -0
  60. package/dist-client/client/components/mention-popup.test.d.ts +1 -0
  61. package/dist-client/client/components/mention-popup.test.js +474 -0
  62. package/dist-client/client/components/mention-popup.test.js.map +1 -0
  63. package/dist-client/client/index.d.ts +11 -0
  64. package/dist-client/client/index.js +12 -0
  65. package/dist-client/client/index.js.map +1 -0
  66. package/dist-client/client/utils/board-edit-patch.d.ts +157 -0
  67. package/dist-client/client/utils/board-edit-patch.js +284 -0
  68. package/dist-client/client/utils/board-edit-patch.js.map +1 -0
  69. package/dist-client/server/service/agentic-loop.d.ts +87 -0
  70. package/dist-client/server/service/agentic-loop.js +276 -0
  71. package/dist-client/server/service/agentic-loop.js.map +1 -0
  72. package/dist-client/server/service/assistant.d.ts +84 -0
  73. package/dist-client/server/service/assistant.js +2220 -0
  74. package/dist-client/server/service/assistant.js.map +1 -0
  75. package/dist-client/server/service/catalog-resolver.d.ts +18 -0
  76. package/dist-client/server/service/catalog-resolver.js +93 -0
  77. package/dist-client/server/service/catalog-resolver.js.map +1 -0
  78. package/dist-client/server/service/component-tree.d.ts +68 -0
  79. package/dist-client/server/service/component-tree.js +68 -0
  80. package/dist-client/server/service/component-tree.js.map +1 -0
  81. package/dist-client/server/service/mention-resolver.d.ts +96 -0
  82. package/dist-client/server/service/mention-resolver.js +172 -0
  83. package/dist-client/server/service/mention-resolver.js.map +1 -0
  84. package/dist-client/server/service/prefab-registry.d.ts +73 -0
  85. package/dist-client/server/service/prefab-registry.js +252 -0
  86. package/dist-client/server/service/prefab-registry.js.map +1 -0
  87. package/dist-client/server/service/styling/clear-tools.d.ts +15 -0
  88. package/dist-client/server/service/styling/clear-tools.js +50 -0
  89. package/dist-client/server/service/styling/clear-tools.js.map +1 -0
  90. package/dist-client/server/service/styling/color-utils.d.ts +43 -0
  91. package/dist-client/server/service/styling/color-utils.js +205 -0
  92. package/dist-client/server/service/styling/color-utils.js.map +1 -0
  93. package/dist-client/server/service/styling/copy-tools.d.ts +27 -0
  94. package/dist-client/server/service/styling/copy-tools.js +78 -0
  95. package/dist-client/server/service/styling/copy-tools.js.map +1 -0
  96. package/dist-client/server/service/styling/effect-tools.d.ts +45 -0
  97. package/dist-client/server/service/styling/effect-tools.js +85 -0
  98. package/dist-client/server/service/styling/effect-tools.js.map +1 -0
  99. package/dist-client/server/service/styling/fill-tools.d.ts +60 -0
  100. package/dist-client/server/service/styling/fill-tools.js +226 -0
  101. package/dist-client/server/service/styling/fill-tools.js.map +1 -0
  102. package/dist-client/server/service/styling/material-tools.d.ts +32 -0
  103. package/dist-client/server/service/styling/material-tools.js +88 -0
  104. package/dist-client/server/service/styling/material-tools.js.map +1 -0
  105. package/dist-client/server/service/styling/read-tools.d.ts +34 -0
  106. package/dist-client/server/service/styling/read-tools.js +42 -0
  107. package/dist-client/server/service/styling/read-tools.js.map +1 -0
  108. package/dist-client/server/service/styling/registry.d.ts +91 -0
  109. package/dist-client/server/service/styling/registry.js +345 -0
  110. package/dist-client/server/service/styling/registry.js.map +1 -0
  111. package/dist-client/server/service/styling/stroke-tools.d.ts +35 -0
  112. package/dist-client/server/service/styling/stroke-tools.js +94 -0
  113. package/dist-client/server/service/styling/stroke-tools.js.map +1 -0
  114. package/dist-client/server/service/styling/text-tools.d.ts +32 -0
  115. package/dist-client/server/service/styling/text-tools.js +101 -0
  116. package/dist-client/server/service/styling/text-tools.js.map +1 -0
  117. package/dist-client/server/service/types.d.ts +265 -0
  118. package/dist-client/server/service/types.js +2 -0
  119. package/dist-client/server/service/types.js.map +1 -0
  120. package/dist-client/server/service/validation/board-model-schema.d.ts +8311 -0
  121. package/dist-client/server/service/validation/board-model-schema.js +402 -0
  122. package/dist-client/server/service/validation/board-model-schema.js.map +1 -0
  123. package/dist-client/server/service/validation/tool-validation.d.ts +31 -0
  124. package/dist-client/server/service/validation/tool-validation.js +193 -0
  125. package/dist-client/server/service/validation/tool-validation.js.map +1 -0
  126. package/dist-client/tsconfig.tsbuildinfo +1 -0
  127. package/dist-server/index.d.ts +23 -0
  128. package/dist-server/index.js +27 -0
  129. package/dist-server/index.js.map +1 -0
  130. package/dist-server/service/agentic-loop.d.ts +87 -0
  131. package/dist-server/service/agentic-loop.js +280 -0
  132. package/dist-server/service/agentic-loop.js.map +1 -0
  133. package/dist-server/service/apply-patch.d.ts +47 -0
  134. package/dist-server/service/apply-patch.js +369 -0
  135. package/dist-server/service/apply-patch.js.map +1 -0
  136. package/dist-server/service/assistant.d.ts +84 -0
  137. package/dist-server/service/assistant.js +2237 -0
  138. package/dist-server/service/assistant.js.map +1 -0
  139. package/dist-server/service/board-ai-resolver.d.ts +54 -0
  140. package/dist-server/service/board-ai-resolver.js +409 -0
  141. package/dist-server/service/board-ai-resolver.js.map +1 -0
  142. package/dist-server/service/catalog-resolver.d.ts +18 -0
  143. package/dist-server/service/catalog-resolver.js +98 -0
  144. package/dist-server/service/catalog-resolver.js.map +1 -0
  145. package/dist-server/service/chat-message/chat-message-broadcast.d.ts +13 -0
  146. package/dist-server/service/chat-message/chat-message-broadcast.js +13 -0
  147. package/dist-server/service/chat-message/chat-message-broadcast.js.map +1 -0
  148. package/dist-server/service/chat-message/chat-message-publish.d.ts +6 -0
  149. package/dist-server/service/chat-message/chat-message-publish.js +27 -0
  150. package/dist-server/service/chat-message/chat-message-publish.js.map +1 -0
  151. package/dist-server/service/chat-message/chat-message-subscription.d.ts +8 -0
  152. package/dist-server/service/chat-message/chat-message-subscription.js +53 -0
  153. package/dist-server/service/chat-message/chat-message-subscription.js.map +1 -0
  154. package/dist-server/service/chat-message/chat-message.d.ts +56 -0
  155. package/dist-server/service/chat-message/chat-message.js +149 -0
  156. package/dist-server/service/chat-message/chat-message.js.map +1 -0
  157. package/dist-server/service/chat-message/index.d.ts +5 -0
  158. package/dist-server/service/chat-message/index.js +12 -0
  159. package/dist-server/service/chat-message/index.js.map +1 -0
  160. package/dist-server/service/chat-session/chat-session.d.ts +31 -0
  161. package/dist-server/service/chat-session/chat-session.js +131 -0
  162. package/dist-server/service/chat-session/chat-session.js.map +1 -0
  163. package/dist-server/service/chat-session/index.d.ts +3 -0
  164. package/dist-server/service/chat-session/index.js +7 -0
  165. package/dist-server/service/chat-session/index.js.map +1 -0
  166. package/dist-server/service/chat-session-participant/chat-session-participant.d.ts +30 -0
  167. package/dist-server/service/chat-session-participant/chat-session-participant.js +112 -0
  168. package/dist-server/service/chat-session-participant/chat-session-participant.js.map +1 -0
  169. package/dist-server/service/chat-session-participant/index.d.ts +4 -0
  170. package/dist-server/service/chat-session-participant/index.js +10 -0
  171. package/dist-server/service/chat-session-participant/index.js.map +1 -0
  172. package/dist-server/service/chat-session-participant/invite-notification.d.ts +27 -0
  173. package/dist-server/service/chat-session-participant/invite-notification.js +30 -0
  174. package/dist-server/service/chat-session-participant/invite-notification.js.map +1 -0
  175. package/dist-server/service/chat-session-participant/presence.d.ts +4 -0
  176. package/dist-server/service/chat-session-participant/presence.js +22 -0
  177. package/dist-server/service/chat-session-participant/presence.js.map +1 -0
  178. package/dist-server/service/chat-session-resolver.d.ts +20 -0
  179. package/dist-server/service/chat-session-resolver.js +356 -0
  180. package/dist-server/service/chat-session-resolver.js.map +1 -0
  181. package/dist-server/service/component-tree.d.ts +68 -0
  182. package/dist-server/service/component-tree.js +73 -0
  183. package/dist-server/service/component-tree.js.map +1 -0
  184. package/dist-server/service/index.d.ts +20 -0
  185. package/dist-server/service/index.js +34 -0
  186. package/dist-server/service/index.js.map +1 -0
  187. package/dist-server/service/mention-marker.d.ts +78 -0
  188. package/dist-server/service/mention-marker.js +162 -0
  189. package/dist-server/service/mention-marker.js.map +1 -0
  190. package/dist-server/service/mention-resolver.d.ts +96 -0
  191. package/dist-server/service/mention-resolver.js +177 -0
  192. package/dist-server/service/mention-resolver.js.map +1 -0
  193. package/dist-server/service/patch-entry/board-patch-broadcast.d.ts +13 -0
  194. package/dist-server/service/patch-entry/board-patch-broadcast.js +13 -0
  195. package/dist-server/service/patch-entry/board-patch-broadcast.js.map +1 -0
  196. package/dist-server/service/patch-entry/board-patch-subscription.d.ts +12 -0
  197. package/dist-server/service/patch-entry/board-patch-subscription.js +63 -0
  198. package/dist-server/service/patch-entry/board-patch-subscription.js.map +1 -0
  199. package/dist-server/service/patch-entry/index.d.ts +4 -0
  200. package/dist-server/service/patch-entry/index.js +12 -0
  201. package/dist-server/service/patch-entry/index.js.map +1 -0
  202. package/dist-server/service/patch-entry/patch-entry.d.ts +16 -0
  203. package/dist-server/service/patch-entry/patch-entry.js +96 -0
  204. package/dist-server/service/patch-entry/patch-entry.js.map +1 -0
  205. package/dist-server/service/prefab-registry.d.ts +73 -0
  206. package/dist-server/service/prefab-registry.js +258 -0
  207. package/dist-server/service/prefab-registry.js.map +1 -0
  208. package/dist-server/service/styling/clear-tools.d.ts +15 -0
  209. package/dist-server/service/styling/clear-tools.js +53 -0
  210. package/dist-server/service/styling/clear-tools.js.map +1 -0
  211. package/dist-server/service/styling/color-utils.d.ts +43 -0
  212. package/dist-server/service/styling/color-utils.js +210 -0
  213. package/dist-server/service/styling/color-utils.js.map +1 -0
  214. package/dist-server/service/styling/copy-tools.d.ts +27 -0
  215. package/dist-server/service/styling/copy-tools.js +81 -0
  216. package/dist-server/service/styling/copy-tools.js.map +1 -0
  217. package/dist-server/service/styling/effect-tools.d.ts +45 -0
  218. package/dist-server/service/styling/effect-tools.js +90 -0
  219. package/dist-server/service/styling/effect-tools.js.map +1 -0
  220. package/dist-server/service/styling/fill-tools.d.ts +60 -0
  221. package/dist-server/service/styling/fill-tools.js +229 -0
  222. package/dist-server/service/styling/fill-tools.js.map +1 -0
  223. package/dist-server/service/styling/material-tools.d.ts +32 -0
  224. package/dist-server/service/styling/material-tools.js +92 -0
  225. package/dist-server/service/styling/material-tools.js.map +1 -0
  226. package/dist-server/service/styling/read-tools.d.ts +34 -0
  227. package/dist-server/service/styling/read-tools.js +45 -0
  228. package/dist-server/service/styling/read-tools.js.map +1 -0
  229. package/dist-server/service/styling/registry.d.ts +91 -0
  230. package/dist-server/service/styling/registry.js +352 -0
  231. package/dist-server/service/styling/registry.js.map +1 -0
  232. package/dist-server/service/styling/stroke-tools.d.ts +35 -0
  233. package/dist-server/service/styling/stroke-tools.js +97 -0
  234. package/dist-server/service/styling/stroke-tools.js.map +1 -0
  235. package/dist-server/service/styling/text-tools.d.ts +32 -0
  236. package/dist-server/service/styling/text-tools.js +105 -0
  237. package/dist-server/service/styling/text-tools.js.map +1 -0
  238. package/dist-server/service/subscribe-auth.d.ts +14 -0
  239. package/dist-server/service/subscribe-auth.js +33 -0
  240. package/dist-server/service/subscribe-auth.js.map +1 -0
  241. package/dist-server/service/types.d.ts +265 -0
  242. package/dist-server/service/types.js +3 -0
  243. package/dist-server/service/types.js.map +1 -0
  244. package/dist-server/service/validation/board-model-schema.d.ts +8311 -0
  245. package/dist-server/service/validation/board-model-schema.js +409 -0
  246. package/dist-server/service/validation/board-model-schema.js.map +1 -0
  247. package/dist-server/service/validation/tool-validation.d.ts +31 -0
  248. package/dist-server/service/validation/tool-validation.js +196 -0
  249. package/dist-server/service/validation/tool-validation.js.map +1 -0
  250. package/dist-server/tsconfig.tsbuildinfo +1 -0
  251. package/package.json +48 -0
  252. package/server/index.ts +23 -0
  253. package/server/service/agentic-loop.test.ts +705 -0
  254. package/server/service/agentic-loop.ts +431 -0
  255. package/server/service/apply-patch-drift.test.ts +829 -0
  256. package/server/service/apply-patch.test.ts +757 -0
  257. package/server/service/apply-patch.ts +411 -0
  258. package/server/service/assistant-integration.test.ts +922 -0
  259. package/server/service/assistant-llm-smoke.test.ts +296 -0
  260. package/server/service/assistant.test.ts +2034 -0
  261. package/server/service/assistant.ts +2378 -0
  262. package/server/service/board-ai-resolver.ts +407 -0
  263. package/server/service/catalog-resolver.test.ts +137 -0
  264. package/server/service/catalog-resolver.ts +102 -0
  265. package/server/service/chat-message/chat-message-broadcast.integration.test.ts +73 -0
  266. package/server/service/chat-message/chat-message-broadcast.test.ts +40 -0
  267. package/server/service/chat-message/chat-message-broadcast.ts +27 -0
  268. package/server/service/chat-message/chat-message-publish.ts +22 -0
  269. package/server/service/chat-message/chat-message-subscription.ts +41 -0
  270. package/server/service/chat-message/chat-message.ts +169 -0
  271. package/server/service/chat-message/index.ts +12 -0
  272. package/server/service/chat-session/chat-session.ts +130 -0
  273. package/server/service/chat-session/index.ts +5 -0
  274. package/server/service/chat-session-participant/chat-session-participant.ts +110 -0
  275. package/server/service/chat-session-participant/index.ts +6 -0
  276. package/server/service/chat-session-participant/invite-notification.test.ts +30 -0
  277. package/server/service/chat-session-participant/invite-notification.ts +48 -0
  278. package/server/service/chat-session-participant/presence.test.ts +39 -0
  279. package/server/service/chat-session-participant/presence.ts +21 -0
  280. package/server/service/chat-session-resolver.ts +312 -0
  281. package/server/service/component-tree.test.ts +248 -0
  282. package/server/service/component-tree.ts +135 -0
  283. package/server/service/index.ts +32 -0
  284. package/server/service/mention-marker.extract.test.ts +61 -0
  285. package/server/service/mention-marker.test.ts +228 -0
  286. package/server/service/mention-marker.ts +184 -0
  287. package/server/service/mention-resolver.test.ts +273 -0
  288. package/server/service/mention-resolver.ts +264 -0
  289. package/server/service/patch-entry/board-patch-broadcast.ts +24 -0
  290. package/server/service/patch-entry/board-patch-subscription.test.ts +40 -0
  291. package/server/service/patch-entry/board-patch-subscription.ts +50 -0
  292. package/server/service/patch-entry/index.ts +12 -0
  293. package/server/service/patch-entry/patch-entry.ts +89 -0
  294. package/server/service/prefab-registry.test.ts +172 -0
  295. package/server/service/prefab-registry.ts +332 -0
  296. package/server/service/styling/clear-tools.test.ts +164 -0
  297. package/server/service/styling/clear-tools.ts +69 -0
  298. package/server/service/styling/color-utils.test.ts +130 -0
  299. package/server/service/styling/color-utils.ts +226 -0
  300. package/server/service/styling/copy-tools.test.ts +152 -0
  301. package/server/service/styling/copy-tools.ts +115 -0
  302. package/server/service/styling/effect-tools.test.ts +277 -0
  303. package/server/service/styling/effect-tools.ts +124 -0
  304. package/server/service/styling/fill-tools.test.ts +331 -0
  305. package/server/service/styling/fill-tools.ts +293 -0
  306. package/server/service/styling/material-tools.test.ts +294 -0
  307. package/server/service/styling/material-tools.ts +114 -0
  308. package/server/service/styling/read-tools.test.ts +78 -0
  309. package/server/service/styling/read-tools.ts +67 -0
  310. package/server/service/styling/registry.test.ts +203 -0
  311. package/server/service/styling/registry.ts +423 -0
  312. package/server/service/styling/stroke-tools.test.ts +245 -0
  313. package/server/service/styling/stroke-tools.ts +145 -0
  314. package/server/service/styling/text-tools.test.ts +253 -0
  315. package/server/service/styling/text-tools.ts +131 -0
  316. package/server/service/styling/tier1-tools.test.ts +300 -0
  317. package/server/service/subscribe-auth.test.ts +57 -0
  318. package/server/service/subscribe-auth.ts +34 -0
  319. package/server/service/types.ts +235 -0
  320. package/server/service/validation/board-model-schema.test.ts +778 -0
  321. package/server/service/validation/board-model-schema.ts +446 -0
  322. package/server/service/validation/tool-validation.ts +228 -0
  323. package/things-factory.config.js +1 -0
  324. package/translations/en.json +49 -0
  325. package/translations/ja.json +48 -0
  326. package/translations/ko.json +49 -0
  327. package/translations/ms.json +48 -0
  328. package/translations/zh.json +48 -0
  329. package/tsconfig.json +9 -0
@@ -0,0 +1,116 @@
1
+ import { receiveRemoteMessage, reconcileSentLine, remoteMessageToLine, type DedupLine } from './chat-echo-dedup'
2
+
3
+ const msg = (id: string, role = 'user', content = 'hi') => ({ id, role, content })
4
+
5
+ describe('remoteMessageToLine', () => {
6
+ it('필드 매핑 + relatedPatchId/toolUsagesJson 변환', () => {
7
+ const line = remoteMessageToLine({
8
+ id: 'm1',
9
+ role: 'assistant',
10
+ content: 'done',
11
+ relatedPatchId: 'p1',
12
+ toolUsagesJson: [{ name: 'x' }]
13
+ })
14
+ expect(line).toMatchObject({ id: 'm1', role: 'assistant', content: 'done', patchId: 'p1' })
15
+ expect(line.toolUsages).toEqual([{ name: 'x' }])
16
+ })
17
+
18
+ it('toolUsagesJson 이 배열 아니면 undefined', () => {
19
+ expect(remoteMessageToLine({ id: 'm', role: 'user', content: 'x', toolUsagesJson: null }).toolUsages).toBeUndefined()
20
+ })
21
+
22
+ it('creator → senderId/senderName (그룹챗 발신자 표시)', () => {
23
+ const line = remoteMessageToLine({ id: 'm', role: 'user', content: 'x', creator: { id: 'u9', name: 'Kim' } })
24
+ expect(line.senderId).toBe('u9')
25
+ expect(line.senderName).toBe('Kim')
26
+ })
27
+
28
+ it('creator 없으면 sender 필드 undefined', () => {
29
+ const line = remoteMessageToLine({ id: 'm', role: 'user', content: 'x', creator: null })
30
+ expect(line.senderId).toBeUndefined()
31
+ expect(line.senderName).toBeUndefined()
32
+ })
33
+ })
34
+
35
+ describe('receiveRemoteMessage — 방송 수신/에코 차단', () => {
36
+ it('신규 id → append + seen 등록', () => {
37
+ const res = receiveRemoteMessage([], new Set(), msg('m1'))!
38
+ expect(res).not.toBeNull()
39
+ expect(res.lines).toHaveLength(1)
40
+ expect(res.lines[0].id).toBe('m1')
41
+ expect(res.seen.has('m1')).toBe(true)
42
+ })
43
+
44
+ it('이미 seen 인 id → null (에코·중복 차단)', () => {
45
+ expect(receiveRemoteMessage([], new Set(['m1']), msg('m1'))).toBeNull()
46
+ })
47
+
48
+ it('id 없는 메시지 → null', () => {
49
+ expect(receiveRemoteMessage([], new Set(), { role: 'user', content: 'x' } as any)).toBeNull()
50
+ expect(receiveRemoteMessage([], new Set(), null)).toBeNull()
51
+ })
52
+
53
+ it('불변 — 원본 배열/셋 미변경', () => {
54
+ const lines: DedupLine[] = []
55
+ const seen = new Set<string>()
56
+ receiveRemoteMessage(lines, seen, msg('m1'))
57
+ expect(lines).toHaveLength(0)
58
+ expect(seen.size).toBe(0)
59
+ })
60
+
61
+ it('다른 참여자 메시지 두 개 순차 append', () => {
62
+ let lines: DedupLine[] = []
63
+ let seen = new Set<string>()
64
+ ;({ lines, seen } = receiveRemoteMessage(lines, seen, msg('a'))!)
65
+ ;({ lines, seen } = receiveRemoteMessage(lines, seen, msg('b'))!)
66
+ expect(lines.map(l => l.id)).toEqual(['a', 'b'])
67
+ })
68
+ })
69
+
70
+ describe('reconcileSentLine — optimistic 라인 조정', () => {
71
+ const optimistic = (): DedupLine[] => [{ _localId: 'L1', role: 'user', content: 'typed' }]
72
+
73
+ it('정상(에코 전): serverId 부여 + seen 등록 + _localId 해제', () => {
74
+ const res = reconcileSentLine(optimistic(), new Set(), 'L1', 'srv1', {})
75
+ expect(res.lines).toHaveLength(1)
76
+ expect(res.lines[0].id).toBe('srv1')
77
+ expect(res.lines[0]._localId).toBeUndefined()
78
+ expect(res.seen.has('srv1')).toBe(true)
79
+ })
80
+
81
+ it('레이스(에코 먼저): serverId 가 이미 seen → optimistic 중복 제거', () => {
82
+ // 에코가 먼저 도착해 같은 메시지가 이미 append + seen 됨
83
+ const lines: DedupLine[] = [
84
+ { id: 'srv1', role: 'user', content: 'typed#enriched' }, // 방송 에코로 들어온 것
85
+ { _localId: 'L1', role: 'user', content: 'typed' } // optimistic 중복
86
+ ]
87
+ const res = reconcileSentLine(lines, new Set(['srv1']), 'L1', 'srv1', {})
88
+ expect(res.lines).toHaveLength(1)
89
+ expect(res.lines[0].id).toBe('srv1')
90
+ expect(res.lines.some(l => l._localId === 'L1')).toBe(false)
91
+ })
92
+
93
+ it('patch 병합 (AI 라인: pending → 실제 응답)', () => {
94
+ const lines: DedupLine[] = [{ _localId: 'A1', role: 'assistant', content: '', pending: true }]
95
+ const res = reconcileSentLine(lines, new Set(), 'A1', 'srv2', {
96
+ content: 'reply',
97
+ pending: false,
98
+ patchId: 'p9'
99
+ })
100
+ expect(res.lines[0]).toMatchObject({ id: 'srv2', content: 'reply', pending: false, patchId: 'p9' })
101
+ })
102
+
103
+ it('serverId 없음(ad-hoc 세션): 라인 유지 + _localId 만 해제, seen 불변', () => {
104
+ const seen = new Set<string>()
105
+ const res = reconcileSentLine(optimistic(), seen, 'L1', undefined, {})
106
+ expect(res.lines[0]._localId).toBeUndefined()
107
+ expect(res.lines[0].id).toBeUndefined()
108
+ expect(res.seen.size).toBe(0)
109
+ })
110
+
111
+ it('해당 _localId 없으면 변화 없음', () => {
112
+ const lines = optimistic()
113
+ const res = reconcileSentLine(lines, new Set(), 'NOPE', 'srv1', {})
114
+ expect(res.lines).toBe(lines)
115
+ })
116
+ })
@@ -0,0 +1,89 @@
1
+ /*
2
+ * 그룹챗 메시지 에코 dedup — 순수 로직.
3
+ *
4
+ * 실시간 방송은 자기 발신 메시지도 되돌려준다(에코). 또 pubsub publish 가 mutation 응답보다
5
+ * 먼저 도착할 수 있어(레이스) optimistic 라인과 방송 라인이 겹칠 수 있다. 이 모듈은 그 조정을
6
+ * **id 기반**으로 결정한다 (서버가 멘션 마커로 content 를 enrich 하므로 content 매칭은 불가).
7
+ *
8
+ * 컴포넌트 상태(this.lines / this._seenMessageIds)에서 분리 → 단위테스트 가능.
9
+ * (C2 의 patch-apply-gate.ts 와 동일한 "순수 게이트" 컨벤션.)
10
+ */
11
+
12
+ /** dedup 에 필요한 최소 라인 형태 (board-ai-chat 의 ChatLine 부분집합). */
13
+ export interface DedupLine {
14
+ id?: string
15
+ _localId?: string
16
+ role: string
17
+ content: string
18
+ senderId?: string
19
+ senderName?: string
20
+ [k: string]: any
21
+ }
22
+
23
+ /** 서버 방송 메시지(필요 필드만). */
24
+ export interface RemoteMessage {
25
+ id?: string
26
+ role: string
27
+ content: string
28
+ relatedPatchId?: string
29
+ toolUsagesJson?: any
30
+ creator?: { id?: string; name?: string } | null
31
+ }
32
+
33
+ /** 방송 메시지 → 라인. */
34
+ export function remoteMessageToLine(m: RemoteMessage): DedupLine {
35
+ return {
36
+ id: m.id,
37
+ role: m.role,
38
+ content: m.content,
39
+ patchId: m.relatedPatchId ?? undefined,
40
+ toolUsages: Array.isArray(m.toolUsagesJson) ? m.toolUsagesJson : undefined,
41
+ senderId: m.creator?.id ?? undefined,
42
+ senderName: m.creator?.name ?? undefined
43
+ }
44
+ }
45
+
46
+ /**
47
+ * 원격(방송) 메시지 수신 결과를 계산.
48
+ * - id 없음 / 이미 seen → 변화 없음(null).
49
+ * - 신규 → seen 에 추가 + 라인 append.
50
+ * 반환: 변경된 { lines, seen } (불변 — 새 배열/셋) 또는 null(무시).
51
+ */
52
+ export function receiveRemoteMessage(
53
+ lines: DedupLine[],
54
+ seen: Set<string>,
55
+ m: RemoteMessage | undefined | null
56
+ ): { lines: DedupLine[]; seen: Set<string> } | null {
57
+ if (!m || !m.id) return null
58
+ if (seen.has(m.id)) return null
59
+ const nextSeen = new Set(seen)
60
+ nextSeen.add(m.id)
61
+ return { lines: [...lines, remoteMessageToLine(m)], seen: nextSeen }
62
+ }
63
+
64
+ /**
65
+ * 전송한 optimistic 라인(_localId)을 서버 id 로 reconcile.
66
+ * - 에코가 먼저 도착해 serverId 가 이미 seen → optimistic 중복 라인 제거.
67
+ * - 아니면 serverId 부여(+ seen 등록) + patch 병합 + _localId 해제.
68
+ * - 해당 _localId 라인이 없으면 변화 없음.
69
+ * 반환: 새 { lines, seen } (불변).
70
+ */
71
+ export function reconcileSentLine(
72
+ lines: DedupLine[],
73
+ seen: Set<string>,
74
+ localId: string,
75
+ serverId: string | undefined,
76
+ patch: Partial<DedupLine>
77
+ ): { lines: DedupLine[]; seen: Set<string> } {
78
+ const idx = lines.findIndex(l => l._localId === localId)
79
+ if (idx < 0) return { lines, seen }
80
+
81
+ const nextLines = [...lines]
82
+ if (serverId && seen.has(serverId)) {
83
+ nextLines.splice(idx, 1)
84
+ return { lines: nextLines, seen }
85
+ }
86
+ const nextSeen = serverId ? new Set(seen).add(serverId) : seen
87
+ nextLines[idx] = { ...nextLines[idx], ...patch, id: serverId ?? nextLines[idx].id, _localId: undefined }
88
+ return { lines: nextLines, seen: nextSeen }
89
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * boardAIChat GraphQL mutation 의 input 객체 빌더.
3
+ *
4
+ * board-ai-chat.ts (Lit / Material Web 의존) 와 분리 — pure 함수만 두면 jest 의 node
5
+ * 환경에서도 import 가능. 회귀 테스트 (selectedRefids 누락 등) 가 단위 테스트로 잠긴다.
6
+ */
7
+
8
+ export interface ChatMutationInputArgs {
9
+ sessionId?: string
10
+ history: Array<{ role: string; content: string }>
11
+ liveBoard: any
12
+ scopes?: string[]
13
+ knownTypes?: string[]
14
+ categories?: string[]
15
+ componentSchemas?: any
16
+ /**
17
+ * 선택된 컴포넌트의 `refid` 배열 — things-scene universal numeric handle.
18
+ * id (데이터 바인딩 이름) 와는 다른 개념.
19
+ */
20
+ selectedRefids: number[]
21
+ /**
22
+ * popup 에서 명시 선택한 #mention 매핑 — {token, refid}.
23
+ * 사용자 textarea 에는 #token 만 보이고, 정확한 refid 는 이 별도 채널로 server 에 전달.
24
+ * server 가 mention map 으로 LLM 에 정확한 refid 정보 첨부 (사용자 노출 0).
25
+ */
26
+ mentions?: Array<{ token: string; refid: number }>
27
+ }
28
+
29
+ /**
30
+ * GraphQL mutation 의 input 객체를 빌드.
31
+ *
32
+ * 핵심 보호 — `selectedRefids` 가 항상 포함되어야 한다. 누락이 사용자에게 보고된
33
+ * "AI 가 선택된 컴포넌트를 모른다" 의 root cause 였다.
34
+ */
35
+ export function buildChatMutationInput(args: ChatMutationInputArgs): Record<string, any> {
36
+ return {
37
+ sessionId: args.sessionId ?? null,
38
+ messages: args.history,
39
+ currentBoard: args.liveBoard ?? null,
40
+ scopes: args.scopes,
41
+ knownTypes: args.knownTypes,
42
+ categories: args.categories,
43
+ componentSchemas: args.componentSchemas,
44
+ selectedRefids: args.selectedRefids,
45
+ ...(args.mentions && args.mentions.length > 0 && { mentions: args.mentions })
46
+ }
47
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * default-catalog-entries.ts 의 client-side hardcoded 카탈로그가 server-side 의
3
+ * prefab-registry / COLOR_PALETTES / LAYOUT_TEMPLATES 와 동기화되어 있는지 검증.
4
+ *
5
+ * 새 default 추가 / 이름 변경 시 server-side 만 바꾸고 client 누락하면 popup 에서
6
+ * 사용자 보지 못해 discoverability 무너짐 — 이 drift 테스트가 즉시 잡음.
7
+ *
8
+ * 향후 GraphQL 'boardAICatalog' query 도입으로 client 가 server 에서 동적 로드하면
9
+ * 이 파일과 default-catalog-entries.ts 모두 제거 가능.
10
+ */
11
+ import { DEFAULT_CATALOG_ENTRIES } from './default-catalog-entries'
12
+ import { listBoardPrefabs } from '../../server/service/prefab-registry'
13
+ import {
14
+ listColorPaletteNames,
15
+ listLayoutTemplateNames
16
+ } from '../../server/service/assistant'
17
+
18
+ describe('Default catalog entries — server/client drift 검증', () => {
19
+ test('client prefab 이름이 server prefab-registry 와 일치', () => {
20
+ const clientPrefabs = DEFAULT_CATALOG_ENTRIES.filter(e => e.kind === 'prefab')
21
+ .map(e => e.name)
22
+ .sort()
23
+ const serverPrefabs = listBoardPrefabs()
24
+ .map(p => p.name)
25
+ .sort()
26
+ expect(clientPrefabs).toEqual(serverPrefabs)
27
+ })
28
+
29
+ test('client palette 이름이 server COLOR_PALETTES 와 일치', () => {
30
+ const clientPalettes = DEFAULT_CATALOG_ENTRIES.filter(e => e.kind === 'palette')
31
+ .map(e => e.name)
32
+ .sort()
33
+ const serverPalettes = listColorPaletteNames().sort()
34
+ expect(clientPalettes).toEqual(serverPalettes)
35
+ })
36
+
37
+ test('client layout 이름이 server LAYOUT_TEMPLATES 와 일치', () => {
38
+ const clientLayouts = DEFAULT_CATALOG_ENTRIES.filter(e => e.kind === 'layout')
39
+ .map(e => e.name)
40
+ .sort()
41
+ const serverLayouts = listLayoutTemplateNames().sort()
42
+ expect(clientLayouts).toEqual(serverLayouts)
43
+ })
44
+
45
+ test('각 항목이 description 보유 (popup UX)', () => {
46
+ for (const e of DEFAULT_CATALOG_ENTRIES) {
47
+ expect(e.description).toBeTruthy()
48
+ }
49
+ })
50
+
51
+ test('항목 이름 unique (kind 안에서)', () => {
52
+ const seen = new Set<string>()
53
+ for (const e of DEFAULT_CATALOG_ENTRIES) {
54
+ const key = `${e.kind}:${e.name}`
55
+ expect(seen.has(key)).toBe(false)
56
+ seen.add(key)
57
+ }
58
+ })
59
+ })
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Default 카탈로그 항목 (client-side hardcoded).
3
+ *
4
+ * 사용자 popup discoverability 용. server-side 카탈로그가 권위 source 이지만, 매번
5
+ * GraphQL 호출해 동적으로 가져오는 대신 default 항목을 client 에 캐시. 호스트 패키지
6
+ * (operato 등) 가 추가 prefab 등록 시 popup 에는 안 나타나지만, 사용자가 직접 이름을
7
+ * 타이핑하면 server-side resolver 가 정상 처리 (catalog-resolver.ts).
8
+ *
9
+ * Drift 경고 — 이 파일과 server-side 의 prefab-registry.ts / COLOR_PALETTES /
10
+ * LAYOUT_TEMPLATES 가 일치해야 한다. 새 default 추가 시 양쪽 동기 (assistant.test.ts
11
+ * 의 tool 카탈로그 회귀 테스트가 server-side drift 는 잡음, client 는 별도 검증
12
+ * 필요). 향후 GraphQL 'boardAICatalog' query 도입 시 이 파일 제거 가능.
13
+ */
14
+ import type { CatalogEntry } from './mention-popup-helpers.js'
15
+
16
+ export const DEFAULT_CATALOG_ENTRIES: CatalogEntry[] = [
17
+ // ── Prefabs (3) ─────────────────────────────────────────
18
+ {
19
+ name: 'monitor-card',
20
+ kind: 'prefab',
21
+ description: 'KPI 모니터링 카드 — 라벨 + 큰 값 + 부제'
22
+ },
23
+ {
24
+ name: 'gauge-panel',
25
+ kind: 'prefab',
26
+ description: '원형 게이지 + 라벨 패널'
27
+ },
28
+ {
29
+ name: 'stat-tile',
30
+ kind: 'prefab',
31
+ description: '강조 컬러 KPI 타일 (큰 숫자)'
32
+ },
33
+ // ── Palettes (7) ────────────────────────────────────────
34
+ {
35
+ name: 'industrial-steel',
36
+ kind: 'palette',
37
+ description: '제조 / 공장 — 강철 + cyan 강조'
38
+ },
39
+ {
40
+ name: 'financial-trader',
41
+ kind: 'palette',
42
+ description: '금융 / 트레이딩 — navy + gold'
43
+ },
44
+ {
45
+ name: 'control-room-dark',
46
+ kind: 'palette',
47
+ description: 'NOC / 관제실 — pure black + 네온'
48
+ },
49
+ {
50
+ name: 'medical-clean',
51
+ kind: 'palette',
52
+ description: '의료 / 헬스케어 — soft white + blue'
53
+ },
54
+ {
55
+ name: 'logistics-warm',
56
+ kind: 'palette',
57
+ description: '물류 / 운송 — warm earth + amber'
58
+ },
59
+ {
60
+ name: 'neon-cyberpunk',
61
+ kind: 'palette',
62
+ description: 'SF / 사이버펑크 — black + magenta/cyan'
63
+ },
64
+ {
65
+ name: 'paper-light',
66
+ kind: 'palette',
67
+ description: '보고서 / 프레젠테이션 — 종이 light'
68
+ },
69
+ // ── Layouts (6) ─────────────────────────────────────────
70
+ {
71
+ name: 'kpi-row',
72
+ kind: 'layout',
73
+ description: '상단 KPI 4-tile + 메인 영역'
74
+ },
75
+ {
76
+ name: '3x3-grid',
77
+ kind: 'layout',
78
+ description: '균등 3×3 그리드 — 9개 동등 단위'
79
+ },
80
+ {
81
+ name: 't-shape-command',
82
+ kind: 'layout',
83
+ description: '관제실 — alarm bar + main + side'
84
+ },
85
+ {
86
+ name: 'side-rail',
87
+ kind: 'layout',
88
+ description: '좌측 narrow + 메인 wide'
89
+ },
90
+ {
91
+ name: 'split-half',
92
+ kind: 'layout',
93
+ description: '50/50 가로 분할 — 비교'
94
+ },
95
+ {
96
+ name: 'header-content-footer',
97
+ kind: 'layout',
98
+ description: '보고서 형식 — 3단'
99
+ }
100
+ ]
@@ -0,0 +1,55 @@
1
+ /**
2
+ * default-slash-templates.ts 정합성 검증.
3
+ *
4
+ * Slash template 은 server-side counterpart 없이 client-only — drift 검증 대상이
5
+ * 아니지만 자체 정합성 (unique name / 비어있지 않은 template / @ 멘션이 카탈로그
6
+ * 항목과 호환) 을 검증.
7
+ */
8
+ import { DEFAULT_SLASH_TEMPLATES } from './default-slash-templates'
9
+ import { DEFAULT_CATALOG_ENTRIES } from './default-catalog-entries'
10
+
11
+ describe('Default slash templates — 정합성', () => {
12
+ test('각 템플릿이 name + template 보유', () => {
13
+ for (const t of DEFAULT_SLASH_TEMPLATES) {
14
+ expect(typeof t.name).toBe('string')
15
+ expect(t.name.length).toBeGreaterThan(0)
16
+ expect(typeof t.template).toBe('string')
17
+ expect(t.template.length).toBeGreaterThan(0)
18
+ }
19
+ })
20
+
21
+ test('이름 unique', () => {
22
+ const seen = new Set<string>()
23
+ for (const t of DEFAULT_SLASH_TEMPLATES) {
24
+ expect(seen.has(t.name)).toBe(false)
25
+ seen.add(t.name)
26
+ }
27
+ })
28
+
29
+ test('description 권장 (popup UX)', () => {
30
+ for (const t of DEFAULT_SLASH_TEMPLATES) {
31
+ expect(t.description).toBeTruthy()
32
+ }
33
+ })
34
+
35
+ test('template 안의 @멘션은 카탈로그에 등록된 이름만 사용', () => {
36
+ const catalogNames = new Set(DEFAULT_CATALOG_ENTRIES.map(e => e.name))
37
+ const pattern = /@([^\s@]+)/g
38
+ for (const t of DEFAULT_SLASH_TEMPLATES) {
39
+ let m: RegExpExecArray | null
40
+ while ((m = pattern.exec(t.template)) !== null) {
41
+ const token = m[1]
42
+ // 알 수 없는 카탈로그 멘션이 들어가면 사용자가 선택해도 의도와 다른 결과
43
+ expect(catalogNames.has(token)).toBe(true)
44
+ }
45
+ }
46
+ })
47
+
48
+ test('name 에는 # / @ / / 같은 trigger char 들어가면 안 됨', () => {
49
+ for (const t of DEFAULT_SLASH_TEMPLATES) {
50
+ expect(t.name).not.toContain('#')
51
+ expect(t.name).not.toContain('@')
52
+ expect(t.name).not.toContain('/')
53
+ }
54
+ })
55
+ })
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Default slash templates — 사용자에게 빠르게 작성된 자연어 prompt 를 끼워주는 단축키.
3
+ *
4
+ * `/grid` → "선택한 컴포넌트들을 3x2 그리드로 정렬해줘" 같이 텍스트가 textarea 에
5
+ * 삽입됨. 사용자는 그대로 보내거나 숫자/카탈로그 이름을 수정해 보낼 수 있다.
6
+ *
7
+ * Slash 는 LLM 우회 명령이 아니다 — AI 가 받는 입력은 일반 자연어 prompt 와 동일.
8
+ * 단지 "이런 명령이 가능하구나" 의 discoverability 를 popup 으로 보여주는 역할.
9
+ *
10
+ * 수정 / 추가 정책:
11
+ * - 한국어 우선 (자연어가 한국어라야 한국 사용자에게 즉시 작동)
12
+ * - 카탈로그 이름 (@monitor-card 등) 가 들어간 템플릿은 server-side resolver 와
13
+ * 자동 호환 — drift 검증은 default-catalog-entries.test.ts 가 잡음
14
+ * - 너무 많이 만들지 말 것 (popup 이 과해짐). 가장 흔한 의도만.
15
+ */
16
+ import type { SlashTemplate } from './mention-popup-helpers.js'
17
+
18
+ export const DEFAULT_SLASH_TEMPLATES: SlashTemplate[] = [
19
+ {
20
+ name: 'grid',
21
+ description: '선택한 컴포넌트들을 그리드로 정렬',
22
+ template: '선택한 컴포넌트들을 3x2 그리드로 정렬해줘'
23
+ },
24
+ {
25
+ name: 'row',
26
+ description: '선택한 컴포넌트들을 한 줄로',
27
+ template: '선택한 컴포넌트들을 한 줄로 배치해줘'
28
+ },
29
+ {
30
+ name: 'column',
31
+ description: '선택한 컴포넌트들을 세로로 일렬',
32
+ template: '선택한 컴포넌트들을 세로로 일렬 배치해줘'
33
+ },
34
+ {
35
+ name: 'palette',
36
+ description: '검증된 색 팔레트로 통일',
37
+ template: '@industrial-steel 팔레트로 보드 전체 통일해줘'
38
+ },
39
+ {
40
+ name: 'atmosphere',
41
+ description: '보드 분위기 변경 (자연어 자유 작성)',
42
+ template: '보드 분위기를 어둡고 차분하게 바꿔줘 (atmosphere primer 활용)'
43
+ },
44
+ {
45
+ name: 'dashboard',
46
+ description: 'KPI 대시보드 기본 구성',
47
+ template: '@kpi-row 레이아웃으로 @stat-tile 4개를 상단에 + 아래 메인 영역 구성해줘'
48
+ },
49
+ {
50
+ name: 'add-monitor',
51
+ description: '모니터링 카드 추가',
52
+ template: '@monitor-card 추가 (제목과 값은 적절히)'
53
+ },
54
+ {
55
+ name: 'fit',
56
+ description: '보드 전체 보이게',
57
+ template: '보드 전체가 보이게 화면 맞춰줘'
58
+ },
59
+ {
60
+ name: 'center',
61
+ description: '선택 컴포넌트 화면 가운데로',
62
+ template: '선택한 컴포넌트를 화면 가운데로 이동해줘'
63
+ },
64
+ {
65
+ name: 'inspect',
66
+ description: '선택 컴포넌트 정보 보기',
67
+ template: '현재 선택된 컴포넌트의 모든 속성을 보여줘'
68
+ },
69
+ {
70
+ name: 'align',
71
+ description: '선택 컴포넌트 정렬 (좌/우/위/아래/가운데)',
72
+ template: '선택한 컴포넌트들을 왼쪽 기준으로 정렬해줘'
73
+ },
74
+ {
75
+ name: 'distribute',
76
+ description: '선택 컴포넌트 간격 균등 분배',
77
+ template: '선택한 컴포넌트들을 가로로 간격 균등하게 분배해줘'
78
+ },
79
+ {
80
+ name: 'duplicate',
81
+ description: '선택 컴포넌트 복제',
82
+ template: '선택한 컴포넌트를 오른쪽에 같은 크기로 하나 더 복제해줘'
83
+ },
84
+ {
85
+ name: 'match-size',
86
+ description: '선택 컴포넌트 크기 통일',
87
+ template: '선택한 컴포넌트들을 첫 번째와 같은 크기로 맞춰줘'
88
+ },
89
+ {
90
+ name: 'z-front',
91
+ description: '선택 컴포넌트 맨 앞으로',
92
+ template: '선택한 컴포넌트를 z-order 맨 앞으로 가져와줘'
93
+ },
94
+ {
95
+ name: 'rotate',
96
+ description: '선택 컴포넌트 회전',
97
+ template: '선택한 컴포넌트를 시계방향으로 90도 회전해줘'
98
+ },
99
+ {
100
+ name: 'z-back',
101
+ description: '선택 컴포넌트 맨 뒤로',
102
+ template: '선택한 컴포넌트를 z-order 맨 뒤로 보내줘'
103
+ },
104
+ {
105
+ name: 'mirror',
106
+ description: '선택 컴포넌트 좌우 반전',
107
+ template: '선택한 컴포넌트를 좌우 대칭으로 반전해줘'
108
+ },
109
+ {
110
+ name: 'group',
111
+ description: '선택 컴포넌트 그룹화',
112
+ template: '선택한 컴포넌트들을 하나의 그룹으로 묶어줘'
113
+ },
114
+ {
115
+ name: 'rename-sequential',
116
+ description: '선택 컴포넌트들에 순번 라벨',
117
+ template: '선택한 컴포넌트들에 1, 2, 3 순번 라벨을 붙여줘'
118
+ },
119
+ {
120
+ name: 'bulk-set',
121
+ description: '선택 컴포넌트 속성 일괄 변경',
122
+ template: '선택한 컴포넌트들의 [속성]을 [값]으로 일괄 설정해줘'
123
+ },
124
+ {
125
+ name: 'cleanup',
126
+ description: '겹침/어색한 위치 자동 정리',
127
+ template: '겹쳐있거나 어색한 위치의 컴포넌트들을 자연스럽게 정리해줘'
128
+ }
129
+ ]