@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,352 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.STYLING_TOOLS = void 0;
4
+ exports.getStylingToolNames = getStylingToolNames;
5
+ exports.buildStylingToolDefinitions = buildStylingToolDefinitions;
6
+ exports.dispatchStylingTool = dispatchStylingTool;
7
+ exports.isStylingTool = isStylingTool;
8
+ const fill_tools_1 = require("./fill-tools");
9
+ const stroke_tools_1 = require("./stroke-tools");
10
+ const text_tools_1 = require("./text-tools");
11
+ const effect_tools_1 = require("./effect-tools");
12
+ const material_tools_1 = require("./material-tools");
13
+ const clear_tools_1 = require("./clear-tools");
14
+ const copy_tools_1 = require("./copy-tools");
15
+ /**
16
+ * 등록된 styling tool 들. 한 tool 씩 점진 이전 중.
17
+ *
18
+ * 마이그레이션 순서: setFill → setStroke → setText/setFont → setShadow /
19
+ * setOpacity / setVisible → setMaterial / setShadowProperties → clearStyle
20
+ * → copyStyle. 각 이전 commit 은 별도 — bisect 로 회귀 추적 용이.
21
+ *
22
+ * spec 작성 규약:
23
+ * - description 은 영문 / 기능 설명
24
+ * - themeNote 는 ⚠ 시작 한국어 정책 단문 (테마 가이드 / 호출 금지 등)
25
+ * - schema 는 LLM 에 그대로 노출되는 JSON Schema
26
+ * - builder 는 builder 함수 직접 사용 (signature 호환). components 인자
27
+ * 안 쓰는 tool 도 그대로 통과.
28
+ */
29
+ exports.STYLING_TOOLS = [
30
+ {
31
+ name: 'setFill',
32
+ description: "Set a component's BACKGROUND/FILL (fillStyle). Use this for fill changes — handles 4 modes (none/solid/gradient/pattern/image) safely with mode-aware updates. Prefer over modifyComponentByRefid for fill-only changes.\n\nMode rules:\n - mode='solid': requires `color`\n - mode='gradient': provide `colorStops` (array of {color,position}) or fall back from `color` (auto-generates 2 stops)\n - mode='pattern' or 'image': requires `image` (URL or asset ref)\n - mode='none': removes fill\n - omitting `mode`: partial update (only color/opacity) — preserves existing mode and other fields.",
33
+ schema: {
34
+ type: 'object',
35
+ properties: {
36
+ refids: {
37
+ type: 'array',
38
+ items: { type: 'number' },
39
+ description: 'Components to update. things-scene auto-assigned numeric handles.'
40
+ },
41
+ mode: {
42
+ type: 'string',
43
+ enum: ['none', 'solid', 'gradient', 'pattern', 'image'],
44
+ description: 'Fill mode dispatcher (synthetic — not stored on the model). Omit for partial update of color/opacity within the existing mode.'
45
+ },
46
+ color: {
47
+ type: 'string',
48
+ description: "Color (hex '#ff0000' / rgb / named 'red' / semantic 'warm-red'). Required for mode='solid'. Output: top-level fillStyle string when mode=solid; nested fillStyle.color when mode=pattern (background tint)."
49
+ },
50
+ gradientType: {
51
+ type: 'string',
52
+ enum: ['linear', 'radial'],
53
+ description: 'Gradient kind (when mode=gradient). Default linear. Stored at fillStyle.gradientType.'
54
+ },
55
+ colorStops: {
56
+ type: 'array',
57
+ items: {
58
+ type: 'object',
59
+ properties: {
60
+ color: { type: 'string' },
61
+ position: { type: 'number', description: '0..1' }
62
+ },
63
+ required: ['color', 'position']
64
+ },
65
+ description: 'Gradient color stops (when mode=gradient). At least 2. Stored at fillStyle.colorStops (canonical things-scene field name).'
66
+ },
67
+ rotation: {
68
+ type: 'number',
69
+ description: 'Gradient rotation in degrees (when mode=gradient). Default 0. Internally converted to radians and stored at fillStyle.rotation.'
70
+ },
71
+ image: {
72
+ type: 'string',
73
+ description: 'Image URL or asset reference (when mode=pattern or mode=image). Stored at fillStyle.image.'
74
+ },
75
+ opacity: {
76
+ type: 'number',
77
+ description: 'Fill alpha 0..1. For mode=solid, encoded as rgba alpha channel in the fillStyle string. For mode=gradient/pattern, stored at fillStyle.opacity.'
78
+ }
79
+ },
80
+ required: ['refids']
81
+ },
82
+ builder: (args, _components) => (0, fill_tools_1.buildSetFillOps)(args)
83
+ },
84
+ {
85
+ name: 'setStroke',
86
+ description: "Set component STROKE / BORDER. Canonical things-scene fields (all top-level on state): strokeStyle (color string), lineWidth (number), lineDash (preset name string), lineCap, lineJoin. dash 는 CSS-친화 alias 도 매핑 — 'dashed'/'dotted'/'dashdot' → things-scene preset. 'solid'/'dash'/'square-dot'/'round-dot'/'dash-dot'/'long-dash'/'long-dash-dot'/'long-dash-dot-dot' 도 가능.",
87
+ themeNote: '⚠ 테마 요청 (북유럽/사이버펑크/미니멀 등) 시 lineWidth/dash/lineCap/lineJoin 은 건드리지 말 것 — 선의 두께·점선 패턴은 디자인 의도를 가진 *구조* 다. strokeStyle (색상) 만 테마 팔레트에 맞춰 변경. 사용자가 명시적으로 두께/점선 변경을 요청한 경우에만 lineWidth/dash 사용.',
88
+ schema: {
89
+ type: 'object',
90
+ properties: {
91
+ refids: { type: 'array', items: { type: 'number' } },
92
+ enabled: {
93
+ type: 'boolean',
94
+ description: 'Synthetic helper — false maps to lineWidth=0 (drawer skips). Not stored on the model itself.'
95
+ },
96
+ strokeStyle: {
97
+ type: 'string',
98
+ description: 'Stroke color (CSS color string — hex / rgb / named / semantic alias). Stored at top-level state.strokeStyle.'
99
+ },
100
+ lineWidth: {
101
+ type: 'number',
102
+ description: 'Stroke width in pixels (0 = no stroke). Stored at top-level state.lineWidth.'
103
+ },
104
+ dash: {
105
+ description: "Dash pattern. CSS-친화 alias 자동 매핑: 'dashed'→'dash', 'dotted'→'square-dot', 'dashdot'→'dash-dot'. things-scene preset 직접 사용도 OK: 'solid' / 'round-dot' / 'square-dot' / 'dash' / 'dash-dot' / 'long-dash' / 'long-dash-dot' / 'long-dash-dot-dot'. Stored at top-level state.lineDash."
106
+ },
107
+ lineCap: {
108
+ type: 'string',
109
+ enum: ['butt', 'round', 'square'],
110
+ description: 'Line cap style. Stored at top-level state.lineCap.'
111
+ },
112
+ lineJoin: {
113
+ type: 'string',
114
+ enum: ['miter', 'round', 'bevel'],
115
+ description: 'Line join style. Stored at top-level state.lineJoin.'
116
+ }
117
+ },
118
+ required: ['refids']
119
+ },
120
+ builder: (args, _components) => (0, stroke_tools_1.buildSetStrokeOps)(args)
121
+ },
122
+ {
123
+ name: 'setText',
124
+ description: 'Change component TEXT CONTENT (state.text). For font styling use setFont separately.',
125
+ themeNote: '⚠ 텍스트는 *콘텐츠* 이지 *스타일* 이 아니다 — 테마 요청 (북유럽/사이버펑크/미니멀 등) 시 호출 금지. 사용자가 텍스트 변경을 명시한 경우에만 사용.',
126
+ schema: {
127
+ type: 'object',
128
+ properties: {
129
+ refids: { type: 'array', items: { type: 'number' } },
130
+ text: { type: 'string' }
131
+ },
132
+ required: ['refids', 'text']
133
+ },
134
+ builder: (args, _components) => (0, text_tools_1.buildSetTextOps)(args)
135
+ },
136
+ {
137
+ name: 'setFont',
138
+ description: 'Set FONT/TEXT STYLE (family / size / weight / style / color / align / baseline / lineHeight). For text content use setText.',
139
+ themeNote: '⚠ 테마 요청 (북유럽/사이버펑크 등) 시 fontSize / fontFamily 는 건드리지 말 것 — 정보 위계와 레이아웃에 영향. fontColor 만 테마 팔레트에 맞춰 변경. 사용자가 명시적으로 폰트 크기/패밀리 변경을 요청한 경우에만 사용.',
140
+ schema: {
141
+ type: 'object',
142
+ properties: {
143
+ refids: { type: 'array', items: { type: 'number' } },
144
+ fontFamily: { type: 'string' },
145
+ fontSize: { type: 'number' },
146
+ fontWeight: {
147
+ description: "'normal' | 'bold' | 'lighter' | 'bolder' | 100..900"
148
+ },
149
+ fontStyle: { type: 'string', enum: ['normal', 'italic', 'oblique'] },
150
+ fontColor: { type: 'string' },
151
+ textAlign: {
152
+ type: 'string',
153
+ enum: ['left', 'center', 'right', 'start', 'end', 'justify']
154
+ },
155
+ textBaseline: {
156
+ type: 'string',
157
+ enum: ['top', 'middle', 'bottom', 'hanging', 'alphabetic', 'ideographic']
158
+ },
159
+ lineHeight: { type: 'number' }
160
+ },
161
+ required: ['refids']
162
+ },
163
+ builder: (args, _components) => (0, text_tools_1.buildSetFontOps)(args)
164
+ },
165
+ {
166
+ name: 'setShadow',
167
+ description: 'Set component SHADOW. Standard CSS box-shadow / Canvas shadow* fields (nested under state.shadow): color, blur, offsetX, offsetY. enabled=false maps to blur=0 + transparent (synthetic helper).',
168
+ schema: {
169
+ type: 'object',
170
+ properties: {
171
+ refids: { type: 'array', items: { type: 'number' } },
172
+ enabled: {
173
+ type: 'boolean',
174
+ description: 'Synthetic helper — false maps to blur=0 + color=transparent. Not stored as a model field.'
175
+ },
176
+ color: {
177
+ type: 'string',
178
+ description: 'Shadow color (CSS color string). Stored at state.shadow.color.'
179
+ },
180
+ blur: {
181
+ type: 'number',
182
+ description: 'Shadow blur radius in pixels (matches CSS box-shadow blur-radius / Canvas shadowBlur). Stored at state.shadow.blur.'
183
+ },
184
+ offsetX: {
185
+ type: 'number',
186
+ description: 'Shadow horizontal offset in pixels (matches CSS box-shadow offset-x / Canvas shadowOffsetX). Stored at state.shadow.offsetX.'
187
+ },
188
+ offsetY: {
189
+ type: 'number',
190
+ description: 'Shadow vertical offset in pixels (matches CSS box-shadow offset-y / Canvas shadowOffsetY). Stored at state.shadow.offsetY.'
191
+ }
192
+ },
193
+ required: ['refids']
194
+ },
195
+ builder: (args, _components) => (0, effect_tools_1.buildSetShadowOps)(args)
196
+ },
197
+ {
198
+ name: 'setOpacity',
199
+ description: 'Set component overall ALPHA (canonical things-scene field state.alpha, 0..1).',
200
+ schema: {
201
+ type: 'object',
202
+ properties: {
203
+ refids: { type: 'array', items: { type: 'number' } },
204
+ alpha: {
205
+ type: 'number',
206
+ description: 'Component overall opacity 0..1 (1 = opaque, 0 = invisible). Stored at top-level state.alpha.'
207
+ }
208
+ },
209
+ required: ['refids', 'alpha']
210
+ },
211
+ builder: (args, _components) => (0, effect_tools_1.buildSetOpacityOps)(args)
212
+ },
213
+ {
214
+ name: 'setVisible',
215
+ description: 'Show/hide components (state.hidden = !visible).',
216
+ schema: {
217
+ type: 'object',
218
+ properties: {
219
+ refids: { type: 'array', items: { type: 'number' } },
220
+ visible: { type: 'boolean' }
221
+ },
222
+ required: ['refids', 'visible']
223
+ },
224
+ builder: (args, _components) => (0, effect_tools_1.buildSetVisibleOps)(args)
225
+ },
226
+ {
227
+ name: 'setMaterial',
228
+ description: 'Set 3D PBR MATERIAL (preset / metalness / roughness / emissive / opacity / envMapIntensity / side). Preset is semantic shortcut: metal/glass/plastic/wood/ceramic/rubber/default. preset alone resets, preset + properties applies overrides on top.',
229
+ schema: {
230
+ type: 'object',
231
+ properties: {
232
+ refids: { type: 'array', items: { type: 'number' } },
233
+ preset: {
234
+ type: 'string',
235
+ enum: ['default', 'metal', 'glass', 'plastic', 'wood', 'ceramic', 'rubber', 'custom']
236
+ },
237
+ metalness: { type: 'number', description: '0..1' },
238
+ roughness: { type: 'number', description: '0..1' },
239
+ emissive: { type: 'string', description: 'glow color (hex / named)' },
240
+ emissiveIntensity: { type: 'number', description: '0..5' },
241
+ opacity: { type: 'number', description: '0..1 (3D-only, separate from component alpha)' },
242
+ envMapIntensity: { type: 'number', description: '0..3' },
243
+ side: { type: 'string', enum: ['double', 'front', 'back'] }
244
+ },
245
+ required: ['refids']
246
+ },
247
+ builder: (args, _components) => (0, material_tools_1.buildSetMaterialOps)(args)
248
+ },
249
+ {
250
+ name: 'setShadowProperties',
251
+ description: 'Set 3D shadow behavior — castShadow / receiveShadow flags on material3d.',
252
+ schema: {
253
+ type: 'object',
254
+ properties: {
255
+ refids: { type: 'array', items: { type: 'number' } },
256
+ castShadow: { type: 'boolean' },
257
+ receiveShadow: { type: 'boolean' }
258
+ },
259
+ required: ['refids']
260
+ },
261
+ builder: (args, _components) => (0, material_tools_1.buildSetShadowPropertiesOps)(args)
262
+ },
263
+ {
264
+ name: 'clearStyle',
265
+ description: 'Reset specific styling aspects to default — useful before applying preset/binding. Aspects: fill/stroke/shadow/material/font/opacity.',
266
+ schema: {
267
+ type: 'object',
268
+ properties: {
269
+ refids: { type: 'array', items: { type: 'number' } },
270
+ aspects: {
271
+ type: 'array',
272
+ items: {
273
+ type: 'string',
274
+ enum: ['fill', 'stroke', 'shadow', 'material', 'font', 'opacity']
275
+ }
276
+ }
277
+ },
278
+ required: ['refids', 'aspects']
279
+ },
280
+ builder: (args, _components) => (0, clear_tools_1.buildClearStyleOps)(args)
281
+ },
282
+ {
283
+ name: 'copyStyle',
284
+ description: "Copy STYLING from one component to others. Use for 'make these components look like that one' patterns. aspects can limit which styling areas to copy (default: all except visibility). Source's styling is read server-side — you don't need getComponentStyle first.",
285
+ schema: {
286
+ type: 'object',
287
+ properties: {
288
+ sourceRefid: { type: 'number', description: 'Component to copy from' },
289
+ targetRefids: {
290
+ type: 'array',
291
+ items: { type: 'number' },
292
+ description: 'Components to copy to (source excluded automatically)'
293
+ },
294
+ aspects: {
295
+ type: 'array',
296
+ items: {
297
+ type: 'string',
298
+ enum: ['fill', 'stroke', 'shadow', 'material', 'font', 'opacity', 'visibility']
299
+ },
300
+ description: 'Which styling aspects to copy. Default: fill/stroke/shadow/material/font/opacity (visibility excluded by default).'
301
+ }
302
+ },
303
+ required: ['sourceRefid', 'targetRefids']
304
+ },
305
+ // copyStyle 은 source lookup 위해 components 배열 필요
306
+ builder: (args, components) => (0, copy_tools_1.buildCopyStyleOps)(args, components)
307
+ }
308
+ ];
309
+ // ── Helpers — 사용처에서 호출 ────────────────────────────────────
310
+ /**
311
+ * registry 에 등록된 모든 styling tool 이름 set.
312
+ * WRITE_TOOL_NAMES 합산용 — assistant.ts 가 isWriteTool 판정 시 styling 영역
313
+ * 부분을 본 set 에 위임.
314
+ */
315
+ function getStylingToolNames(registry = exports.STYLING_TOOLS) {
316
+ return new Set(registry.map(s => s.name));
317
+ }
318
+ /**
319
+ * LLM 에 노출할 tool 정의 배열.
320
+ * buildBoardEditTools 가 styling 부분을 본 함수 결과로 채운다.
321
+ */
322
+ function buildStylingToolDefinitions(registry = exports.STYLING_TOOLS) {
323
+ return registry.map(spec => ({
324
+ name: spec.name,
325
+ description: spec.themeNote
326
+ ? `${spec.description} ${spec.themeNote}`
327
+ : spec.description,
328
+ parameters: spec.schema
329
+ }));
330
+ }
331
+ /**
332
+ * AIToolCall name 에 매칭되는 styling tool 의 builder 호출 → BoardEditOp[] | null.
333
+ *
334
+ * 반환:
335
+ * - 매칭 spec 없음 → null (caller 가 다른 카테고리 dispatch 시도)
336
+ * - 빈 ops (validation 실패 등) → null (assistant.ts 의 기존 동작과 동일)
337
+ * - 정상 → BoardEditOp[]
338
+ *
339
+ * components 인자는 copyStyle 같은 source-lookup 필요 tool 에만 의미 있음.
340
+ */
341
+ function dispatchStylingTool(name, args, components = [], registry = exports.STYLING_TOOLS) {
342
+ const spec = registry.find(s => s.name === name);
343
+ if (!spec)
344
+ return null;
345
+ const result = spec.builder(args, components);
346
+ return result.ops.length > 0 ? result.ops : null;
347
+ }
348
+ /** name 이 styling tool 로 등록되어 있는가. */
349
+ function isStylingTool(name, registry = exports.STYLING_TOOLS) {
350
+ return registry.some(s => s.name === name);
351
+ }
352
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../server/service/styling/registry.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Styling tool registry — 선언형 단일 진실 (single source of truth).\n *\n * 동기:\n * 기존 styling tool 추가 시 4-5곳 (WRITE_TOOL_NAMES set / tools 배열 정의 /\n * dispatch case / validation case / system prompt 안내) 에 같은 정보가 산재해\n * 동기화 누락 위험. 신규 tool 마다 4곳 boilerplate.\n *\n * 본 registry 가 한 곳에 모음 — 위 모든 사용처가 STYLING_TOOLS 에서 자동 도출.\n *\n * 마이그레이션:\n * STYLING_TOOLS 는 초기엔 비어있고, 한 tool 씩 점진 이전한다. 각 단계마다\n * 기존 hardcoded 정의/dispatch 와 byte-단위 동등성을 단위 테스트가 잠금.\n *\n * 비-styling write tool (addComponent / modifyBoard / alignComponents 등) 은\n * 본 registry 의 영역이 아니다 — 의미·구조가 styling 패턴과 달라 별도 dispatch.\n */\nimport type { BoardEditOp } from '../types'\nimport { buildSetFillOps } from './fill-tools'\nimport { buildSetStrokeOps } from './stroke-tools'\nimport { buildSetTextOps, buildSetFontOps } from './text-tools'\nimport { buildSetShadowOps, buildSetOpacityOps, buildSetVisibleOps } from './effect-tools'\nimport { buildSetMaterialOps, buildSetShadowPropertiesOps } from './material-tools'\nimport { buildClearStyleOps } from './clear-tools'\nimport { buildCopyStyleOps } from './copy-tools'\n\nexport interface OpsResult {\n ops: BoardEditOp[]\n errors: string[]\n}\n\n/**\n * 한 styling tool 의 모든 메타데이터 + 빌더.\n *\n * description 과 themeNote 는 별도 — 자동 합성될 때 description 본문은 영문/기능\n * 위주로 짧게, themeNote 는 한국어 ⚠ 정책 단문. 분리해 두면 미래에 i18n / 정책\n * 일괄 변경 시 검색-치환이 쉽다.\n */\nexport interface StylingToolSpec {\n /** AIToolCall.name 과 매칭. unique. */\n name: string\n /** 기능 설명 (영문 위주). */\n description: string\n /**\n * 선택 — ⚠ 정책 / 테마 가이드 단문. 빌드 시 description 끝에 자동 부착.\n * 예: '⚠ 테마 요청 시 width/dash 건드리지 말 것.'\n */\n themeNote?: string\n /** JSON Schema for parameters — LLM tool schema 에 그대로 노출. */\n schema: any\n /**\n * AIToolCall.arguments → OpsResult 변환.\n * components 는 copyStyle 처럼 source lookup 필요한 tool 만 사용. 다른 tool 은\n * 두 번째 인자 무시하면 됨.\n */\n builder: (args: any, components: ReadonlyArray<any>) => OpsResult\n}\n\n/**\n * 등록된 styling tool 들. 한 tool 씩 점진 이전 중.\n *\n * 마이그레이션 순서: setFill → setStroke → setText/setFont → setShadow /\n * setOpacity / setVisible → setMaterial / setShadowProperties → clearStyle\n * → copyStyle. 각 이전 commit 은 별도 — bisect 로 회귀 추적 용이.\n *\n * spec 작성 규약:\n * - description 은 영문 / 기능 설명\n * - themeNote 는 ⚠ 시작 한국어 정책 단문 (테마 가이드 / 호출 금지 등)\n * - schema 는 LLM 에 그대로 노출되는 JSON Schema\n * - builder 는 builder 함수 직접 사용 (signature 호환). components 인자\n * 안 쓰는 tool 도 그대로 통과.\n */\nexport const STYLING_TOOLS: StylingToolSpec[] = [\n {\n name: 'setFill',\n description:\n \"Set a component's BACKGROUND/FILL (fillStyle). Use this for fill changes — handles 4 modes (none/solid/gradient/pattern/image) safely with mode-aware updates. Prefer over modifyComponentByRefid for fill-only changes.\\n\\nMode rules:\\n - mode='solid': requires `color`\\n - mode='gradient': provide `colorStops` (array of {color,position}) or fall back from `color` (auto-generates 2 stops)\\n - mode='pattern' or 'image': requires `image` (URL or asset ref)\\n - mode='none': removes fill\\n - omitting `mode`: partial update (only color/opacity) — preserves existing mode and other fields.\",\n schema: {\n type: 'object',\n properties: {\n refids: {\n type: 'array',\n items: { type: 'number' },\n description: 'Components to update. things-scene auto-assigned numeric handles.'\n },\n mode: {\n type: 'string',\n enum: ['none', 'solid', 'gradient', 'pattern', 'image'],\n description: 'Fill mode dispatcher (synthetic — not stored on the model). Omit for partial update of color/opacity within the existing mode.'\n },\n color: {\n type: 'string',\n description: \"Color (hex '#ff0000' / rgb / named 'red' / semantic 'warm-red'). Required for mode='solid'. Output: top-level fillStyle string when mode=solid; nested fillStyle.color when mode=pattern (background tint).\"\n },\n gradientType: {\n type: 'string',\n enum: ['linear', 'radial'],\n description: 'Gradient kind (when mode=gradient). Default linear. Stored at fillStyle.gradientType.'\n },\n colorStops: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n color: { type: 'string' },\n position: { type: 'number', description: '0..1' }\n },\n required: ['color', 'position']\n },\n description: 'Gradient color stops (when mode=gradient). At least 2. Stored at fillStyle.colorStops (canonical things-scene field name).'\n },\n rotation: {\n type: 'number',\n description: 'Gradient rotation in degrees (when mode=gradient). Default 0. Internally converted to radians and stored at fillStyle.rotation.'\n },\n image: {\n type: 'string',\n description: 'Image URL or asset reference (when mode=pattern or mode=image). Stored at fillStyle.image.'\n },\n opacity: {\n type: 'number',\n description: 'Fill alpha 0..1. For mode=solid, encoded as rgba alpha channel in the fillStyle string. For mode=gradient/pattern, stored at fillStyle.opacity.'\n }\n },\n required: ['refids']\n },\n builder: (args, _components) => buildSetFillOps(args)\n },\n {\n name: 'setStroke',\n description:\n \"Set component STROKE / BORDER. Canonical things-scene fields (all top-level on state): strokeStyle (color string), lineWidth (number), lineDash (preset name string), lineCap, lineJoin. dash 는 CSS-친화 alias 도 매핑 — 'dashed'/'dotted'/'dashdot' → things-scene preset. 'solid'/'dash'/'square-dot'/'round-dot'/'dash-dot'/'long-dash'/'long-dash-dot'/'long-dash-dot-dot' 도 가능.\",\n themeNote:\n '⚠ 테마 요청 (북유럽/사이버펑크/미니멀 등) 시 lineWidth/dash/lineCap/lineJoin 은 건드리지 말 것 — 선의 두께·점선 패턴은 디자인 의도를 가진 *구조* 다. strokeStyle (색상) 만 테마 팔레트에 맞춰 변경. 사용자가 명시적으로 두께/점선 변경을 요청한 경우에만 lineWidth/dash 사용.',\n schema: {\n type: 'object',\n properties: {\n refids: { type: 'array', items: { type: 'number' } },\n enabled: {\n type: 'boolean',\n description: 'Synthetic helper — false maps to lineWidth=0 (drawer skips). Not stored on the model itself.'\n },\n strokeStyle: {\n type: 'string',\n description: 'Stroke color (CSS color string — hex / rgb / named / semantic alias). Stored at top-level state.strokeStyle.'\n },\n lineWidth: {\n type: 'number',\n description: 'Stroke width in pixels (0 = no stroke). Stored at top-level state.lineWidth.'\n },\n dash: {\n description:\n \"Dash pattern. CSS-친화 alias 자동 매핑: 'dashed'→'dash', 'dotted'→'square-dot', 'dashdot'→'dash-dot'. things-scene preset 직접 사용도 OK: 'solid' / 'round-dot' / 'square-dot' / 'dash' / 'dash-dot' / 'long-dash' / 'long-dash-dot' / 'long-dash-dot-dot'. Stored at top-level state.lineDash.\"\n },\n lineCap: {\n type: 'string',\n enum: ['butt', 'round', 'square'],\n description: 'Line cap style. Stored at top-level state.lineCap.'\n },\n lineJoin: {\n type: 'string',\n enum: ['miter', 'round', 'bevel'],\n description: 'Line join style. Stored at top-level state.lineJoin.'\n }\n },\n required: ['refids']\n },\n builder: (args, _components) => buildSetStrokeOps(args)\n },\n {\n name: 'setText',\n description: 'Change component TEXT CONTENT (state.text). For font styling use setFont separately.',\n themeNote:\n '⚠ 텍스트는 *콘텐츠* 이지 *스타일* 이 아니다 — 테마 요청 (북유럽/사이버펑크/미니멀 등) 시 호출 금지. 사용자가 텍스트 변경을 명시한 경우에만 사용.',\n schema: {\n type: 'object',\n properties: {\n refids: { type: 'array', items: { type: 'number' } },\n text: { type: 'string' }\n },\n required: ['refids', 'text']\n },\n builder: (args, _components) => buildSetTextOps(args)\n },\n {\n name: 'setFont',\n description:\n 'Set FONT/TEXT STYLE (family / size / weight / style / color / align / baseline / lineHeight). For text content use setText.',\n themeNote:\n '⚠ 테마 요청 (북유럽/사이버펑크 등) 시 fontSize / fontFamily 는 건드리지 말 것 — 정보 위계와 레이아웃에 영향. fontColor 만 테마 팔레트에 맞춰 변경. 사용자가 명시적으로 폰트 크기/패밀리 변경을 요청한 경우에만 사용.',\n schema: {\n type: 'object',\n properties: {\n refids: { type: 'array', items: { type: 'number' } },\n fontFamily: { type: 'string' },\n fontSize: { type: 'number' },\n fontWeight: {\n description: \"'normal' | 'bold' | 'lighter' | 'bolder' | 100..900\"\n },\n fontStyle: { type: 'string', enum: ['normal', 'italic', 'oblique'] },\n fontColor: { type: 'string' },\n textAlign: {\n type: 'string',\n enum: ['left', 'center', 'right', 'start', 'end', 'justify']\n },\n textBaseline: {\n type: 'string',\n enum: ['top', 'middle', 'bottom', 'hanging', 'alphabetic', 'ideographic']\n },\n lineHeight: { type: 'number' }\n },\n required: ['refids']\n },\n builder: (args, _components) => buildSetFontOps(args)\n },\n {\n name: 'setShadow',\n description:\n 'Set component SHADOW. Standard CSS box-shadow / Canvas shadow* fields (nested under state.shadow): color, blur, offsetX, offsetY. enabled=false maps to blur=0 + transparent (synthetic helper).',\n schema: {\n type: 'object',\n properties: {\n refids: { type: 'array', items: { type: 'number' } },\n enabled: {\n type: 'boolean',\n description: 'Synthetic helper — false maps to blur=0 + color=transparent. Not stored as a model field.'\n },\n color: {\n type: 'string',\n description: 'Shadow color (CSS color string). Stored at state.shadow.color.'\n },\n blur: {\n type: 'number',\n description: 'Shadow blur radius in pixels (matches CSS box-shadow blur-radius / Canvas shadowBlur). Stored at state.shadow.blur.'\n },\n offsetX: {\n type: 'number',\n description: 'Shadow horizontal offset in pixels (matches CSS box-shadow offset-x / Canvas shadowOffsetX). Stored at state.shadow.offsetX.'\n },\n offsetY: {\n type: 'number',\n description: 'Shadow vertical offset in pixels (matches CSS box-shadow offset-y / Canvas shadowOffsetY). Stored at state.shadow.offsetY.'\n }\n },\n required: ['refids']\n },\n builder: (args, _components) => buildSetShadowOps(args)\n },\n {\n name: 'setOpacity',\n description: 'Set component overall ALPHA (canonical things-scene field state.alpha, 0..1).',\n schema: {\n type: 'object',\n properties: {\n refids: { type: 'array', items: { type: 'number' } },\n alpha: {\n type: 'number',\n description: 'Component overall opacity 0..1 (1 = opaque, 0 = invisible). Stored at top-level state.alpha.'\n }\n },\n required: ['refids', 'alpha']\n },\n builder: (args, _components) => buildSetOpacityOps(args)\n },\n {\n name: 'setVisible',\n description: 'Show/hide components (state.hidden = !visible).',\n schema: {\n type: 'object',\n properties: {\n refids: { type: 'array', items: { type: 'number' } },\n visible: { type: 'boolean' }\n },\n required: ['refids', 'visible']\n },\n builder: (args, _components) => buildSetVisibleOps(args)\n },\n {\n name: 'setMaterial',\n description:\n 'Set 3D PBR MATERIAL (preset / metalness / roughness / emissive / opacity / envMapIntensity / side). Preset is semantic shortcut: metal/glass/plastic/wood/ceramic/rubber/default. preset alone resets, preset + properties applies overrides on top.',\n schema: {\n type: 'object',\n properties: {\n refids: { type: 'array', items: { type: 'number' } },\n preset: {\n type: 'string',\n enum: ['default', 'metal', 'glass', 'plastic', 'wood', 'ceramic', 'rubber', 'custom']\n },\n metalness: { type: 'number', description: '0..1' },\n roughness: { type: 'number', description: '0..1' },\n emissive: { type: 'string', description: 'glow color (hex / named)' },\n emissiveIntensity: { type: 'number', description: '0..5' },\n opacity: { type: 'number', description: '0..1 (3D-only, separate from component alpha)' },\n envMapIntensity: { type: 'number', description: '0..3' },\n side: { type: 'string', enum: ['double', 'front', 'back'] }\n },\n required: ['refids']\n },\n builder: (args, _components) => buildSetMaterialOps(args)\n },\n {\n name: 'setShadowProperties',\n description: 'Set 3D shadow behavior — castShadow / receiveShadow flags on material3d.',\n schema: {\n type: 'object',\n properties: {\n refids: { type: 'array', items: { type: 'number' } },\n castShadow: { type: 'boolean' },\n receiveShadow: { type: 'boolean' }\n },\n required: ['refids']\n },\n builder: (args, _components) => buildSetShadowPropertiesOps(args)\n },\n {\n name: 'clearStyle',\n description:\n 'Reset specific styling aspects to default — useful before applying preset/binding. Aspects: fill/stroke/shadow/material/font/opacity.',\n schema: {\n type: 'object',\n properties: {\n refids: { type: 'array', items: { type: 'number' } },\n aspects: {\n type: 'array',\n items: {\n type: 'string',\n enum: ['fill', 'stroke', 'shadow', 'material', 'font', 'opacity']\n }\n }\n },\n required: ['refids', 'aspects']\n },\n builder: (args, _components) => buildClearStyleOps(args)\n },\n {\n name: 'copyStyle',\n description:\n \"Copy STYLING from one component to others. Use for 'make these components look like that one' patterns. aspects can limit which styling areas to copy (default: all except visibility). Source's styling is read server-side — you don't need getComponentStyle first.\",\n schema: {\n type: 'object',\n properties: {\n sourceRefid: { type: 'number', description: 'Component to copy from' },\n targetRefids: {\n type: 'array',\n items: { type: 'number' },\n description: 'Components to copy to (source excluded automatically)'\n },\n aspects: {\n type: 'array',\n items: {\n type: 'string',\n enum: ['fill', 'stroke', 'shadow', 'material', 'font', 'opacity', 'visibility']\n },\n description:\n 'Which styling aspects to copy. Default: fill/stroke/shadow/material/font/opacity (visibility excluded by default).'\n }\n },\n required: ['sourceRefid', 'targetRefids']\n },\n // copyStyle 은 source lookup 위해 components 배열 필요\n builder: (args, components) => buildCopyStyleOps(args, components as any[])\n }\n]\n\n// ── Helpers — 사용처에서 호출 ────────────────────────────────────\n\n/**\n * registry 에 등록된 모든 styling tool 이름 set.\n * WRITE_TOOL_NAMES 합산용 — assistant.ts 가 isWriteTool 판정 시 styling 영역\n * 부분을 본 set 에 위임.\n */\nexport function getStylingToolNames(\n registry: ReadonlyArray<StylingToolSpec> = STYLING_TOOLS\n): Set<string> {\n return new Set(registry.map(s => s.name))\n}\n\n/**\n * LLM 에 노출할 tool 정의 배열.\n * buildBoardEditTools 가 styling 부분을 본 함수 결과로 채운다.\n */\nexport function buildStylingToolDefinitions(\n registry: ReadonlyArray<StylingToolSpec> = STYLING_TOOLS\n): Array<{ name: string; description: string; parameters: any }> {\n return registry.map(spec => ({\n name: spec.name,\n description: spec.themeNote\n ? `${spec.description} ${spec.themeNote}`\n : spec.description,\n parameters: spec.schema\n }))\n}\n\n/**\n * AIToolCall name 에 매칭되는 styling tool 의 builder 호출 → BoardEditOp[] | null.\n *\n * 반환:\n * - 매칭 spec 없음 → null (caller 가 다른 카테고리 dispatch 시도)\n * - 빈 ops (validation 실패 등) → null (assistant.ts 의 기존 동작과 동일)\n * - 정상 → BoardEditOp[]\n *\n * components 인자는 copyStyle 같은 source-lookup 필요 tool 에만 의미 있음.\n */\nexport function dispatchStylingTool(\n name: string,\n args: any,\n components: ReadonlyArray<any> = [],\n registry: ReadonlyArray<StylingToolSpec> = STYLING_TOOLS\n): BoardEditOp[] | null {\n const spec = registry.find(s => s.name === name)\n if (!spec) return null\n const result = spec.builder(args, components)\n return result.ops.length > 0 ? result.ops : null\n}\n\n/** name 이 styling tool 로 등록되어 있는가. */\nexport function isStylingTool(\n name: string,\n registry: ReadonlyArray<StylingToolSpec> = STYLING_TOOLS\n): boolean {\n return registry.some(s => s.name === name)\n}\n"]}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * setStroke — 컴포넌트 테두리 / 라인 스타일.
3
+ *
4
+ * **things-scene 의 실제 데이터 형식 (drawer/stroke.ts 검증):**
5
+ * - `state.strokeStyle: string` (색상 — Canvas API 의 strokeStyle 에 직접 대입되는 CSS color)
6
+ * - `state.lineWidth: number` (top-level)
7
+ * - `state.lineDash: string` (top-level — preset 이름. drawer 가 lineWidth 기반으로 배열 변환)
8
+ * valid: 'solid' | 'round-dot' | 'square-dot' | 'dash' | 'dash-dot' | 'long-dash' | 'long-dash-dot' | 'long-dash-dot-dot'
9
+ * - `state.lineCap: 'butt' | 'round' | 'square'` (top-level)
10
+ * - `state.lineJoin: 'miter' | 'round' | 'bevel'` (top-level)
11
+ *
12
+ * 인터페이스의 `StrokeStyle` 객체 구조는 stale. 사용처는 모두 top-level 직접 접근.
13
+ *
14
+ * 입력 단순화 — AI 친화적 alias 도 받아 things-scene preset 으로 매핑:
15
+ * AI 'dashed' → 'dash', 'dotted' → 'square-dot', 'dashdot' → 'dash-dot'.
16
+ */
17
+ import type { BoardEditOp } from '../types';
18
+ export interface SetStrokeInput {
19
+ refids: number[];
20
+ /** synthetic — false 면 lineWidth=0 으로 매핑 (drawer 가 안 그림). 모델에 enabled 필드는 안 박힘. */
21
+ enabled?: boolean;
22
+ /** state.strokeStyle 의 canonical 이름. CSS color string. */
23
+ strokeStyle?: string;
24
+ /** state.lineWidth 의 canonical 이름. pixels. */
25
+ lineWidth?: number;
26
+ /** preset 이름. AI 친화 alias 자동 매핑 — 'dashed' / 'dotted' / 'dashdot' 도 가능. */
27
+ dash?: 'solid' | 'dashed' | 'dotted' | 'dashdot' | 'round-dot' | 'square-dot' | 'dash' | 'dash-dot' | 'long-dash' | 'long-dash-dot' | 'long-dash-dot-dot' | string;
28
+ lineCap?: 'butt' | 'round' | 'square';
29
+ lineJoin?: 'miter' | 'round' | 'bevel';
30
+ }
31
+ export interface SetStrokeResult {
32
+ ops: BoardEditOp[];
33
+ errors: string[];
34
+ }
35
+ export declare function buildSetStrokeOps(input: SetStrokeInput): SetStrokeResult;
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildSetStrokeOps = buildSetStrokeOps;
4
+ const color_utils_1 = require("./color-utils");
5
+ /** AI 친화 alias → things-scene 의 실제 preset 이름. */
6
+ const DASH_ALIASES = {
7
+ dashed: 'dash',
8
+ dotted: 'square-dot',
9
+ dashdot: 'dash-dot'
10
+ };
11
+ const VALID_DASH = new Set([
12
+ 'solid',
13
+ 'round-dot',
14
+ 'square-dot',
15
+ 'dash',
16
+ 'dash-dot',
17
+ 'long-dash',
18
+ 'long-dash-dot',
19
+ 'long-dash-dot-dot'
20
+ ]);
21
+ // Canvas CanvasRenderingContext2D 가 허용하는 lineCap / lineJoin 만.
22
+ // 잘못된 값은 Canvas 가 silent ignore → AI 가 'flat' / 'cap' 등을 보내도
23
+ // 효과 없음. 명시 검증으로 ops 미실행 + 명확한 에러.
24
+ const VALID_LINE_CAP = new Set(['butt', 'round', 'square']);
25
+ const VALID_LINE_JOIN = new Set(['miter', 'round', 'bevel']);
26
+ function buildSetStrokeOps(input) {
27
+ if (!Array.isArray(input.refids) || input.refids.length === 0) {
28
+ return { ops: [], errors: ['refids must be a non-empty array of numbers'] };
29
+ }
30
+ const refids = input.refids.filter(n => typeof n === 'number' && Number.isFinite(n));
31
+ if (refids.length === 0)
32
+ return { ops: [], errors: ['No valid numeric refids'] };
33
+ const noOp = input.enabled === undefined &&
34
+ input.strokeStyle === undefined &&
35
+ input.lineWidth === undefined &&
36
+ input.dash === undefined &&
37
+ input.lineCap === undefined &&
38
+ input.lineJoin === undefined;
39
+ if (noOp) {
40
+ return { ops: [], errors: ['No stroke changes specified'] };
41
+ }
42
+ // 검증
43
+ if (input.strokeStyle !== undefined) {
44
+ const v = (0, color_utils_1.validateColor)(input.strokeStyle);
45
+ if (!v.valid)
46
+ return { ops: [], errors: [v.error ?? 'Invalid strokeStyle'] };
47
+ }
48
+ if (input.lineWidth !== undefined && (input.lineWidth < 0 || !Number.isFinite(input.lineWidth))) {
49
+ return { ops: [], errors: [`Invalid lineWidth: ${input.lineWidth}`] };
50
+ }
51
+ if (input.lineCap !== undefined && !VALID_LINE_CAP.has(input.lineCap)) {
52
+ return {
53
+ ops: [],
54
+ errors: [`Unknown lineCap: '${input.lineCap}'. Valid: ${[...VALID_LINE_CAP].join(' / ')}.`]
55
+ };
56
+ }
57
+ if (input.lineJoin !== undefined && !VALID_LINE_JOIN.has(input.lineJoin)) {
58
+ return {
59
+ ops: [],
60
+ errors: [`Unknown lineJoin: '${input.lineJoin}'. Valid: ${[...VALID_LINE_JOIN].join(' / ')}.`]
61
+ };
62
+ }
63
+ // dash 정규화 — alias → 실제 preset → 검증
64
+ let dashValue;
65
+ if (input.dash !== undefined) {
66
+ const raw = String(input.dash);
67
+ const resolved = DASH_ALIASES[raw] ?? raw;
68
+ if (!VALID_DASH.has(resolved)) {
69
+ return {
70
+ ops: [],
71
+ errors: [
72
+ `Unknown dash: '${input.dash}'. Valid: ${[...VALID_DASH].join(' / ')} (or alias: dashed/dotted/dashdot).`
73
+ ]
74
+ };
75
+ }
76
+ dashValue = resolved;
77
+ }
78
+ // patch 구성 — canonical 이름 그대로. 매핑 X. (color 만 normalizeColor 통과 — 입력 정규화는 mapping 이 아닌 처리)
79
+ const patch = {};
80
+ if (input.strokeStyle !== undefined)
81
+ patch.strokeStyle = (0, color_utils_1.normalizeColor)(input.strokeStyle);
82
+ if (input.lineWidth !== undefined)
83
+ patch.lineWidth = input.lineWidth;
84
+ if (dashValue !== undefined)
85
+ patch.lineDash = dashValue;
86
+ if (input.lineCap !== undefined)
87
+ patch.lineCap = input.lineCap;
88
+ if (input.lineJoin !== undefined)
89
+ patch.lineJoin = input.lineJoin;
90
+ // synthetic enabled — false 면 lineWidth 0 으로 변환 (drawer 가 안 그림 — drawer/stroke.ts:9)
91
+ if (input.enabled === false) {
92
+ patch.lineWidth = 0;
93
+ }
94
+ const ops = refids.map(refid => ({ op: 'modify', refid, patch }));
95
+ return { ops, errors: [] };
96
+ }
97
+ //# sourceMappingURL=stroke-tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stroke-tools.js","sourceRoot":"","sources":["../../../server/service/styling/stroke-tools.ts"],"names":[],"mappings":";;AA0EA,8CAsEC;AA/HD,+CAA6D;AAiC7D,iDAAiD;AACjD,MAAM,YAAY,GAA2B;IAC3C,MAAM,EAAE,MAAM;IACd,MAAM,EAAE,YAAY;IACpB,OAAO,EAAE,UAAU;CACpB,CAAA;AAED,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC;IACzB,OAAO;IACP,WAAW;IACX,YAAY;IACZ,MAAM;IACN,UAAU;IACV,WAAW;IACX,eAAe;IACf,mBAAmB;CACpB,CAAC,CAAA;AAEF,+DAA+D;AAC/D,6DAA6D;AAC7D,mCAAmC;AACnC,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAA;AAC3D,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAA;AAE5D,SAAgB,iBAAiB,CAAC,KAAqB;IACrD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9D,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,6CAA6C,CAAC,EAAE,CAAA;IAC7E,CAAC;IACD,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAA;IACpF,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,yBAAyB,CAAC,EAAE,CAAA;IAEhF,MAAM,IAAI,GACR,KAAK,CAAC,OAAO,KAAK,SAAS;QAC3B,KAAK,CAAC,WAAW,KAAK,SAAS;QAC/B,KAAK,CAAC,SAAS,KAAK,SAAS;QAC7B,KAAK,CAAC,IAAI,KAAK,SAAS;QACxB,KAAK,CAAC,OAAO,KAAK,SAAS;QAC3B,KAAK,CAAC,QAAQ,KAAK,SAAS,CAAA;IAC9B,IAAI,IAAI,EAAE,CAAC;QACT,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,6BAA6B,CAAC,EAAE,CAAA;IAC7D,CAAC;IAED,KAAK;IACL,IAAI,KAAK,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;QACpC,MAAM,CAAC,GAAG,IAAA,2BAAa,EAAC,KAAK,CAAC,WAAW,CAAC,CAAA;QAC1C,IAAI,CAAC,CAAC,CAAC,KAAK;YAAE,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,qBAAqB,CAAC,EAAE,CAAA;IAC9E,CAAC;IACD,IAAI,KAAK,CAAC,SAAS,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC;QAChG,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,sBAAsB,KAAK,CAAC,SAAS,EAAE,CAAC,EAAE,CAAA;IACvE,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QACtE,OAAO;YACL,GAAG,EAAE,EAAE;YACP,MAAM,EAAE,CAAC,qBAAqB,KAAK,CAAC,OAAO,aAAa,CAAC,GAAG,cAAc,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC;SAC5F,CAAA;IACH,CAAC;IACD,IAAI,KAAK,CAAC,QAAQ,KAAK,SAAS,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzE,OAAO;YACL,GAAG,EAAE,EAAE;YACP,MAAM,EAAE,CAAC,sBAAsB,KAAK,CAAC,QAAQ,aAAa,CAAC,GAAG,eAAe,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC;SAC/F,CAAA;IACH,CAAC;IAED,oCAAoC;IACpC,IAAI,SAA6B,CAAA;IACjC,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC7B,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QAC9B,MAAM,QAAQ,GAAG,YAAY,CAAC,GAAG,CAAC,IAAI,GAAG,CAAA;QACzC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC9B,OAAO;gBACL,GAAG,EAAE,EAAE;gBACP,MAAM,EAAE;oBACN,kBAAkB,KAAK,CAAC,IAAI,aAAa,CAAC,GAAG,UAAU,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,qCAAqC;iBAC1G;aACF,CAAA;QACH,CAAC;QACD,SAAS,GAAG,QAAQ,CAAA;IACtB,CAAC;IAED,2FAA2F;IAC3F,MAAM,KAAK,GAAQ,EAAE,CAAA;IACrB,IAAI,KAAK,CAAC,WAAW,KAAK,SAAS;QAAE,KAAK,CAAC,WAAW,GAAG,IAAA,4BAAc,EAAC,KAAK,CAAC,WAAW,CAAC,CAAA;IAC1F,IAAI,KAAK,CAAC,SAAS,KAAK,SAAS;QAAE,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC,SAAS,CAAA;IACpE,IAAI,SAAS,KAAK,SAAS;QAAE,KAAK,CAAC,QAAQ,GAAG,SAAS,CAAA;IACvD,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS;QAAE,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAA;IAC9D,IAAI,KAAK,CAAC,QAAQ,KAAK,SAAS;QAAE,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAA;IAEjE,qFAAqF;IACrF,IAAI,KAAK,CAAC,OAAO,KAAK,KAAK,EAAE,CAAC;QAC5B,KAAK,CAAC,SAAS,GAAG,CAAC,CAAA;IACrB,CAAC;IAED,MAAM,GAAG,GAAkB,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,CAAA;IAChF,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,CAAA;AAC5B,CAAC","sourcesContent":["/**\n * setStroke — 컴포넌트 테두리 / 라인 스타일.\n *\n * **things-scene 의 실제 데이터 형식 (drawer/stroke.ts 검증):**\n * - `state.strokeStyle: string` (색상 — Canvas API 의 strokeStyle 에 직접 대입되는 CSS color)\n * - `state.lineWidth: number` (top-level)\n * - `state.lineDash: string` (top-level — preset 이름. drawer 가 lineWidth 기반으로 배열 변환)\n * valid: 'solid' | 'round-dot' | 'square-dot' | 'dash' | 'dash-dot' | 'long-dash' | 'long-dash-dot' | 'long-dash-dot-dot'\n * - `state.lineCap: 'butt' | 'round' | 'square'` (top-level)\n * - `state.lineJoin: 'miter' | 'round' | 'bevel'` (top-level)\n *\n * 인터페이스의 `StrokeStyle` 객체 구조는 stale. 사용처는 모두 top-level 직접 접근.\n *\n * 입력 단순화 — AI 친화적 alias 도 받아 things-scene preset 으로 매핑:\n * AI 'dashed' → 'dash', 'dotted' → 'square-dot', 'dashdot' → 'dash-dot'.\n */\nimport type { BoardEditOp } from '../types'\nimport { normalizeColor, validateColor } from './color-utils'\n\nexport interface SetStrokeInput {\n refids: number[]\n /** synthetic — false 면 lineWidth=0 으로 매핑 (drawer 가 안 그림). 모델에 enabled 필드는 안 박힘. */\n enabled?: boolean\n /** state.strokeStyle 의 canonical 이름. CSS color string. */\n strokeStyle?: string\n /** state.lineWidth 의 canonical 이름. pixels. */\n lineWidth?: number\n /** preset 이름. AI 친화 alias 자동 매핑 — 'dashed' / 'dotted' / 'dashdot' 도 가능. */\n dash?:\n | 'solid'\n | 'dashed'\n | 'dotted'\n | 'dashdot'\n | 'round-dot'\n | 'square-dot'\n | 'dash'\n | 'dash-dot'\n | 'long-dash'\n | 'long-dash-dot'\n | 'long-dash-dot-dot'\n | string\n lineCap?: 'butt' | 'round' | 'square'\n lineJoin?: 'miter' | 'round' | 'bevel'\n}\n\nexport interface SetStrokeResult {\n ops: BoardEditOp[]\n errors: string[]\n}\n\n/** AI 친화 alias → things-scene 의 실제 preset 이름. */\nconst DASH_ALIASES: Record<string, string> = {\n dashed: 'dash',\n dotted: 'square-dot',\n dashdot: 'dash-dot'\n}\n\nconst VALID_DASH = new Set([\n 'solid',\n 'round-dot',\n 'square-dot',\n 'dash',\n 'dash-dot',\n 'long-dash',\n 'long-dash-dot',\n 'long-dash-dot-dot'\n])\n\n// Canvas CanvasRenderingContext2D 가 허용하는 lineCap / lineJoin 만.\n// 잘못된 값은 Canvas 가 silent ignore → AI 가 'flat' / 'cap' 등을 보내도\n// 효과 없음. 명시 검증으로 ops 미실행 + 명확한 에러.\nconst VALID_LINE_CAP = new Set(['butt', 'round', 'square'])\nconst VALID_LINE_JOIN = new Set(['miter', 'round', 'bevel'])\n\nexport function buildSetStrokeOps(input: SetStrokeInput): SetStrokeResult {\n if (!Array.isArray(input.refids) || input.refids.length === 0) {\n return { ops: [], errors: ['refids must be a non-empty array of numbers'] }\n }\n const refids = input.refids.filter(n => typeof n === 'number' && Number.isFinite(n))\n if (refids.length === 0) return { ops: [], errors: ['No valid numeric refids'] }\n\n const noOp =\n input.enabled === undefined &&\n input.strokeStyle === undefined &&\n input.lineWidth === undefined &&\n input.dash === undefined &&\n input.lineCap === undefined &&\n input.lineJoin === undefined\n if (noOp) {\n return { ops: [], errors: ['No stroke changes specified'] }\n }\n\n // 검증\n if (input.strokeStyle !== undefined) {\n const v = validateColor(input.strokeStyle)\n if (!v.valid) return { ops: [], errors: [v.error ?? 'Invalid strokeStyle'] }\n }\n if (input.lineWidth !== undefined && (input.lineWidth < 0 || !Number.isFinite(input.lineWidth))) {\n return { ops: [], errors: [`Invalid lineWidth: ${input.lineWidth}`] }\n }\n if (input.lineCap !== undefined && !VALID_LINE_CAP.has(input.lineCap)) {\n return {\n ops: [],\n errors: [`Unknown lineCap: '${input.lineCap}'. Valid: ${[...VALID_LINE_CAP].join(' / ')}.`]\n }\n }\n if (input.lineJoin !== undefined && !VALID_LINE_JOIN.has(input.lineJoin)) {\n return {\n ops: [],\n errors: [`Unknown lineJoin: '${input.lineJoin}'. Valid: ${[...VALID_LINE_JOIN].join(' / ')}.`]\n }\n }\n\n // dash 정규화 — alias → 실제 preset → 검증\n let dashValue: string | undefined\n if (input.dash !== undefined) {\n const raw = String(input.dash)\n const resolved = DASH_ALIASES[raw] ?? raw\n if (!VALID_DASH.has(resolved)) {\n return {\n ops: [],\n errors: [\n `Unknown dash: '${input.dash}'. Valid: ${[...VALID_DASH].join(' / ')} (or alias: dashed/dotted/dashdot).`\n ]\n }\n }\n dashValue = resolved\n }\n\n // patch 구성 — canonical 이름 그대로. 매핑 X. (color 만 normalizeColor 통과 — 입력 정규화는 mapping 이 아닌 처리)\n const patch: any = {}\n if (input.strokeStyle !== undefined) patch.strokeStyle = normalizeColor(input.strokeStyle)\n if (input.lineWidth !== undefined) patch.lineWidth = input.lineWidth\n if (dashValue !== undefined) patch.lineDash = dashValue\n if (input.lineCap !== undefined) patch.lineCap = input.lineCap\n if (input.lineJoin !== undefined) patch.lineJoin = input.lineJoin\n\n // synthetic enabled — false 면 lineWidth 0 으로 변환 (drawer 가 안 그림 — drawer/stroke.ts:9)\n if (input.enabled === false) {\n patch.lineWidth = 0\n }\n\n const ops: BoardEditOp[] = refids.map(refid => ({ op: 'modify', refid, patch }))\n return { ops, errors: [] }\n}\n"]}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * setText / setFont — 컴포넌트의 텍스트 내용 / 폰트 스타일.
3
+ *
4
+ * setText: state.text 변경 (내용)
5
+ * setFont: state.fontFamily / fontSize / fontWeight / fontStyle / fontColor /
6
+ * textAlign / textBaseline / lineHeight 변경 (폰트 스타일)
7
+ *
8
+ * 두 tool 분리 — 의도가 다름. 내용 변경 vs 시각 스타일.
9
+ */
10
+ import type { BoardEditOp } from '../types';
11
+ export interface SetTextInput {
12
+ refids: number[];
13
+ text: string;
14
+ }
15
+ export interface SetFontInput {
16
+ refids: number[];
17
+ fontFamily?: string;
18
+ fontSize?: number;
19
+ fontWeight?: 'normal' | 'bold' | 'lighter' | 'bolder' | string | number;
20
+ fontStyle?: 'normal' | 'italic' | 'oblique';
21
+ fontColor?: string;
22
+ /** Canvas API 표준 — 'justify' 는 Canvas 미지원이라 제외. */
23
+ textAlign?: 'left' | 'center' | 'right' | 'start' | 'end';
24
+ textBaseline?: 'top' | 'middle' | 'bottom' | 'hanging' | 'alphabetic' | 'ideographic';
25
+ lineHeight?: number;
26
+ }
27
+ export interface OpsResult {
28
+ ops: BoardEditOp[];
29
+ errors: string[];
30
+ }
31
+ export declare function buildSetTextOps(input: SetTextInput): OpsResult;
32
+ export declare function buildSetFontOps(input: SetFontInput): OpsResult;