@agent-native/core 0.112.0 → 0.113.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (490) hide show
  1. package/corpus/README.md +4 -2
  2. package/corpus/core/CHANGELOG.md +22 -0
  3. package/corpus/core/docs/AGENTS.md +7 -0
  4. package/corpus/core/docs/content/integrations.mdx +63 -2
  5. package/corpus/core/docs/design/provider-integration-authority.md +117 -0
  6. package/corpus/core/package.json +6 -1
  7. package/corpus/core/scripts/materialize-source-corpus.mjs +9 -2
  8. package/corpus/core/src/cli/atomic-json-file.ts +324 -0
  9. package/corpus/core/src/cli/claude-code-participant.ts +515 -0
  10. package/corpus/core/src/cli/code-agent-executor.ts +18 -10
  11. package/corpus/core/src/cli/code-agent-runs.concurrent-worker.ts +33 -0
  12. package/corpus/core/src/cli/code-agent-runs.ts +55 -62
  13. package/corpus/core/src/cli/codex-cli-participant.ts +496 -0
  14. package/corpus/core/src/cli/multi-frontier-runs.ts +1246 -135
  15. package/corpus/core/src/client/chat/ChatHistoryList.tsx +7 -404
  16. package/corpus/core/src/integrations/index.ts +10 -0
  17. package/corpus/core/src/integrations/slack-oauth.ts +1 -0
  18. package/corpus/core/src/integrations/webhook-delivery.ts +55 -0
  19. package/corpus/core/src/notifications/channels.ts +4 -10
  20. package/corpus/core/src/provider-api/actions/custom-provider-registration.ts +197 -0
  21. package/corpus/core/src/provider-api/actions/delete-staged-dataset.ts +3 -42
  22. package/corpus/core/src/provider-api/actions/list-staged-datasets.ts +3 -45
  23. package/corpus/core/src/provider-api/actions/provider-api-audit.ts +88 -0
  24. package/corpus/core/src/provider-api/actions/provider-api.ts +460 -0
  25. package/corpus/core/src/provider-api/actions/query-staged-dataset.ts +3 -140
  26. package/corpus/core/src/provider-api/actions/staged-datasets.ts +359 -0
  27. package/corpus/core/src/provider-api/gong.ts +108 -0
  28. package/corpus/core/src/scripts/docs/source-search.ts +2 -1
  29. package/corpus/core/src/server/google-oauth-credentials.ts +80 -3
  30. package/corpus/core/src/server/index.ts +6 -0
  31. package/corpus/core/src/styles/chat-history-list.css +1 -313
  32. package/corpus/core/src/templates/default/.agents/skills/actions/SKILL.md +32 -12
  33. package/corpus/core/src/templates/default/.agents/skills/agent-native-docs/SKILL.md +10 -0
  34. package/corpus/core/src/templates/default/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  35. package/corpus/core/src/templates/default/.agents/skills/integration-webhooks/SKILL.md +323 -0
  36. package/corpus/core/src/templates/default/.agents/skills/onboarding/SKILL.md +56 -0
  37. package/corpus/core/src/templates/default/.agents/skills/secrets/SKILL.md +273 -0
  38. package/corpus/core/src/templates/default/.agents/skills/self-modifying-code/SKILL.md +11 -2
  39. package/corpus/core/src/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  40. package/corpus/core/src/templates/default/AGENTS.md +25 -18
  41. package/corpus/core/src/templates/headless/.agents/skills/actions/SKILL.md +512 -0
  42. package/corpus/core/src/templates/headless/.agents/skills/agent-native-docs/SKILL.md +10 -0
  43. package/corpus/core/src/templates/headless/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  44. package/corpus/core/src/templates/headless/.agents/skills/integration-webhooks/SKILL.md +323 -0
  45. package/corpus/core/src/templates/headless/.agents/skills/secrets/SKILL.md +273 -0
  46. package/corpus/core/src/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  47. package/corpus/core/src/templates/headless/AGENTS.md +7 -0
  48. package/corpus/core/src/templates/workspace-core/.agents/skills/actions/SKILL.md +32 -12
  49. package/corpus/core/src/templates/workspace-core/.agents/skills/agent-native-docs/SKILL.md +10 -0
  50. package/corpus/core/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
  51. package/corpus/core/src/templates/workspace-core/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  52. package/corpus/core/src/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +11 -2
  53. package/corpus/core/src/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  54. package/corpus/core/src/templates/workspace-core/AGENTS.md +4 -0
  55. package/corpus/core/src/templates/workspace-root/AGENTS.md +4 -0
  56. package/corpus/templates/analytics/.agents/skills/actions/SKILL.md +32 -12
  57. package/corpus/templates/analytics/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  58. package/corpus/templates/analytics/.agents/skills/self-modifying-code/SKILL.md +11 -2
  59. package/corpus/templates/analytics/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  60. package/corpus/templates/analytics/actions/delete-staged-dataset.ts +3 -23
  61. package/corpus/templates/analytics/actions/list-staged-datasets.ts +3 -27
  62. package/corpus/templates/analytics/actions/provider-api-catalog.ts +16 -19
  63. package/corpus/templates/analytics/actions/provider-api-docs.ts +3 -5
  64. package/corpus/templates/analytics/actions/provider-api-request.ts +162 -185
  65. package/corpus/templates/analytics/actions/query-staged-dataset.ts +3 -47
  66. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +52 -161
  67. package/corpus/templates/assets/.agents/skills/actions/SKILL.md +32 -12
  68. package/corpus/templates/assets/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  69. package/corpus/templates/assets/.agents/skills/self-modifying-code/SKILL.md +11 -2
  70. package/corpus/templates/assets/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  71. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +63 -160
  72. package/corpus/templates/brain/.agents/skills/actions/SKILL.md +32 -12
  73. package/corpus/templates/brain/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  74. package/corpus/templates/brain/.agents/skills/self-modifying-code/SKILL.md +11 -2
  75. package/corpus/templates/brain/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  76. package/corpus/templates/brain/actions/_schemas.ts +13 -7
  77. package/corpus/templates/brain/actions/delete-staged-dataset.ts +3 -24
  78. package/corpus/templates/brain/actions/list-staged-datasets.ts +3 -28
  79. package/corpus/templates/brain/actions/provider-api-catalog.ts +5 -11
  80. package/corpus/templates/brain/actions/provider-api-docs.ts +3 -5
  81. package/corpus/templates/brain/actions/provider-api-request.ts +3 -37
  82. package/corpus/templates/brain/actions/query-staged-dataset.ts +3 -48
  83. package/corpus/templates/brain/actions/update-source.ts +5 -1
  84. package/corpus/templates/brain/app/components/layout/Sidebar.tsx +48 -154
  85. package/corpus/templates/brain/app/lib/brain.ts +55 -0
  86. package/corpus/templates/brain/app/lib/ingest-handoff.ts +35 -0
  87. package/corpus/templates/brain/changelog/2026-07-19-slack-backfills-can-now-join-configured-public-channels-befo.md +6 -0
  88. package/corpus/templates/brain/changelog/2026-07-19-slack-backfills-now-continue-across-the-configured-number-of.md +6 -0
  89. package/corpus/templates/brain/changelog/2026-07-19-slack-backfills-now-start-with-a-safe-four-week-window-and-r.md +6 -0
  90. package/corpus/templates/brain/server/lib/connectors.ts +53 -2
  91. package/corpus/templates/brain/server/lib/slack-channel-membership.ts +44 -0
  92. package/corpus/templates/brain/shared/slack-source-config.ts +60 -0
  93. package/corpus/templates/calendar/.agents/skills/actions/SKILL.md +32 -12
  94. package/corpus/templates/calendar/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  95. package/corpus/templates/calendar/.agents/skills/self-modifying-code/SKILL.md +11 -2
  96. package/corpus/templates/calendar/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  97. package/corpus/templates/calendar/actions/delete-staged-dataset.ts +3 -24
  98. package/corpus/templates/calendar/actions/list-staged-datasets.ts +3 -28
  99. package/corpus/templates/calendar/actions/provider-api-catalog.ts +5 -11
  100. package/corpus/templates/calendar/actions/provider-api-docs.ts +3 -5
  101. package/corpus/templates/calendar/actions/provider-api-request.ts +3 -37
  102. package/corpus/templates/calendar/actions/query-staged-dataset.ts +3 -48
  103. package/corpus/templates/calendar/server/handlers/gong.ts +6 -61
  104. package/corpus/templates/calendar/server/handlers/google-auth.ts +10 -8
  105. package/corpus/templates/calendar/server/lib/google-calendar.ts +7 -45
  106. package/corpus/templates/chat/.agents/skills/actions/SKILL.md +32 -12
  107. package/corpus/templates/chat/.agents/skills/agent-native-docs/SKILL.md +10 -0
  108. package/corpus/templates/chat/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  109. package/corpus/templates/chat/.agents/skills/self-modifying-code/SKILL.md +11 -2
  110. package/corpus/templates/chat/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  111. package/corpus/templates/chat/AGENTS.md +5 -2
  112. package/corpus/templates/chat/app/components/layout/Sidebar.tsx +51 -157
  113. package/corpus/templates/clips/.agents/skills/actions/SKILL.md +32 -12
  114. package/corpus/templates/clips/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  115. package/corpus/templates/clips/.agents/skills/self-modifying-code/SKILL.md +11 -2
  116. package/corpus/templates/clips/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  117. package/corpus/templates/clips/AGENTS.md +11 -0
  118. package/corpus/templates/clips/actions/export-to-brain.ts +505 -171
  119. package/corpus/templates/clips/changelog/2026-07-19-ready-clips-transcripts-can-be-backfilled-to-brain-for-a-bou.md +6 -0
  120. package/corpus/templates/clips/server/lib/calendar-event-meetings.ts +1 -1
  121. package/corpus/templates/clips/server/lib/google-calendar-client.ts +12 -22
  122. package/corpus/templates/clips/server/lib/google-calendar-oauth.ts +9 -6
  123. package/corpus/templates/clips/server/lib/slack-oauth.ts +12 -47
  124. package/corpus/templates/clips/server/routes/_agent-native/google/auth-url.get.ts +2 -6
  125. package/corpus/templates/clips/server/routes/api/auth/google-calendar.get.ts +4 -4
  126. package/corpus/templates/content/.agents/skills/actions/SKILL.md +32 -12
  127. package/corpus/templates/content/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  128. package/corpus/templates/content/.agents/skills/self-modifying-code/SKILL.md +11 -2
  129. package/corpus/templates/content/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  130. package/corpus/templates/content/actions/delete-staged-dataset.ts +3 -24
  131. package/corpus/templates/content/actions/list-staged-datasets.ts +3 -28
  132. package/corpus/templates/content/actions/provider-api-catalog.ts +5 -11
  133. package/corpus/templates/content/actions/provider-api-docs.ts +3 -5
  134. package/corpus/templates/content/actions/provider-api-request.ts +3 -37
  135. package/corpus/templates/content/actions/query-staged-dataset.ts +3 -48
  136. package/corpus/templates/design/.agents/skills/actions/SKILL.md +32 -12
  137. package/corpus/templates/design/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  138. package/corpus/templates/design/.agents/skills/self-modifying-code/SKILL.md +11 -2
  139. package/corpus/templates/design/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  140. package/corpus/templates/design/actions/delete-staged-dataset.ts +4 -23
  141. package/corpus/templates/design/actions/list-staged-datasets.ts +4 -27
  142. package/corpus/templates/design/actions/provider-api-catalog.ts +15 -18
  143. package/corpus/templates/design/actions/provider-api-docs.ts +57 -48
  144. package/corpus/templates/design/actions/provider-api-request.ts +140 -164
  145. package/corpus/templates/design/actions/query-staged-dataset.ts +4 -47
  146. package/corpus/templates/dispatch/.agents/skills/actions/SKILL.md +32 -12
  147. package/corpus/templates/dispatch/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  148. package/corpus/templates/dispatch/.agents/skills/self-modifying-code/SKILL.md +11 -2
  149. package/corpus/templates/dispatch/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  150. package/corpus/templates/dispatch/actions/delete-staged-dataset.ts +4 -23
  151. package/corpus/templates/dispatch/actions/list-staged-datasets.ts +4 -30
  152. package/corpus/templates/dispatch/actions/provider-api-catalog.ts +15 -18
  153. package/corpus/templates/dispatch/actions/provider-api-docs.ts +57 -48
  154. package/corpus/templates/dispatch/actions/provider-api-request.ts +138 -163
  155. package/corpus/templates/dispatch/actions/query-staged-dataset.ts +4 -47
  156. package/corpus/templates/forms/.agents/skills/actions/SKILL.md +32 -12
  157. package/corpus/templates/forms/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  158. package/corpus/templates/forms/.agents/skills/self-modifying-code/SKILL.md +11 -2
  159. package/corpus/templates/forms/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  160. package/corpus/templates/forms/server/lib/integrations.ts +23 -22
  161. package/corpus/templates/macros/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  162. package/corpus/templates/macros/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  163. package/corpus/templates/mail/.agents/skills/actions/SKILL.md +32 -12
  164. package/corpus/templates/mail/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  165. package/corpus/templates/mail/.agents/skills/self-modifying-code/SKILL.md +11 -2
  166. package/corpus/templates/mail/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  167. package/corpus/templates/mail/actions/delete-staged-dataset.ts +4 -23
  168. package/corpus/templates/mail/actions/list-staged-datasets.ts +4 -27
  169. package/corpus/templates/mail/actions/provider-api-catalog.ts +15 -18
  170. package/corpus/templates/mail/actions/provider-api-docs.ts +57 -48
  171. package/corpus/templates/mail/actions/provider-api-request.ts +146 -173
  172. package/corpus/templates/mail/actions/query-staged-dataset.ts +4 -47
  173. package/corpus/templates/mail/server/handlers/gong.ts +6 -61
  174. package/corpus/templates/mail/server/lib/google-auth.ts +10 -13
  175. package/corpus/templates/plan/.agents/skills/actions/SKILL.md +32 -12
  176. package/corpus/templates/plan/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  177. package/corpus/templates/plan/.agents/skills/self-modifying-code/SKILL.md +11 -2
  178. package/corpus/templates/plan/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  179. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +47 -156
  180. package/corpus/templates/slides/.agents/skills/actions/SKILL.md +32 -12
  181. package/corpus/templates/slides/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  182. package/corpus/templates/slides/.agents/skills/self-modifying-code/SKILL.md +11 -2
  183. package/corpus/templates/slides/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  184. package/corpus/templates/slides/actions/delete-staged-dataset.ts +4 -23
  185. package/corpus/templates/slides/actions/list-staged-datasets.ts +4 -27
  186. package/corpus/templates/slides/actions/provider-api-catalog.ts +15 -18
  187. package/corpus/templates/slides/actions/provider-api-docs.ts +57 -48
  188. package/corpus/templates/slides/actions/provider-api-request.ts +145 -172
  189. package/corpus/templates/slides/actions/query-staged-dataset.ts +4 -47
  190. package/corpus/templates/slides/server/lib/google-docs-oauth.ts +11 -36
  191. package/corpus/templates/tasks/.agents/skills/actions/SKILL.md +32 -12
  192. package/corpus/templates/tasks/.agents/skills/agent-native-docs/SKILL.md +10 -0
  193. package/corpus/templates/tasks/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  194. package/corpus/templates/tasks/.agents/skills/self-modifying-code/SKILL.md +11 -2
  195. package/corpus/templates/tasks/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  196. package/corpus/toolkit/CHANGELOG.md +125 -0
  197. package/corpus/toolkit/README.md +60 -0
  198. package/corpus/toolkit/export-snapshot.json +35 -0
  199. package/corpus/toolkit/migration-manifest.json +4 -0
  200. package/corpus/toolkit/package.json +281 -0
  201. package/corpus/toolkit/scripts/finalize-build.mjs +55 -0
  202. package/corpus/toolkit/src/app-shell/header-actions.tsx +84 -0
  203. package/corpus/toolkit/src/app-shell/index.ts +1 -0
  204. package/corpus/toolkit/src/chat-history/ChatHistoryList.tsx +469 -0
  205. package/corpus/toolkit/src/chat-history/index.ts +7 -0
  206. package/corpus/toolkit/src/chat-history.css +314 -0
  207. package/corpus/toolkit/src/collab-ui/AgentPresenceChip.tsx +69 -0
  208. package/corpus/toolkit/src/collab-ui/LiveCursorOverlay.tsx +241 -0
  209. package/corpus/toolkit/src/collab-ui/PresenceBar.tsx +346 -0
  210. package/corpus/toolkit/src/collab-ui/RecentEditHighlights.tsx +225 -0
  211. package/corpus/toolkit/src/collab-ui/RemoteSelectionRings.tsx +227 -0
  212. package/corpus/toolkit/src/collab-ui/agent-identity.ts +16 -0
  213. package/corpus/toolkit/src/collab-ui/index.ts +39 -0
  214. package/corpus/toolkit/src/collab-ui/lead-client.ts +32 -0
  215. package/corpus/toolkit/src/collab-ui/types.ts +96 -0
  216. package/corpus/toolkit/src/composer/AgentComposerFrame.tsx +59 -0
  217. package/corpus/toolkit/src/composer/ComposerPlusMenu.tsx +983 -0
  218. package/corpus/toolkit/src/composer/MentionPopover.tsx +412 -0
  219. package/corpus/toolkit/src/composer/PastedTextChip.tsx +150 -0
  220. package/corpus/toolkit/src/composer/PromptComposer.tsx +718 -0
  221. package/corpus/toolkit/src/composer/RealtimeVoiceMode.tsx +856 -0
  222. package/corpus/toolkit/src/composer/TiptapComposer.tsx +2936 -0
  223. package/corpus/toolkit/src/composer/VoiceButton.tsx +255 -0
  224. package/corpus/toolkit/src/composer/asset-picker-url.ts +80 -0
  225. package/corpus/toolkit/src/composer/attachment-accept.ts +71 -0
  226. package/corpus/toolkit/src/composer/draft-key.ts +7 -0
  227. package/corpus/toolkit/src/composer/extensions/FileReference.tsx +55 -0
  228. package/corpus/toolkit/src/composer/extensions/MentionReference.tsx +93 -0
  229. package/corpus/toolkit/src/composer/extensions/SkillReference.tsx +56 -0
  230. package/corpus/toolkit/src/composer/index.ts +79 -0
  231. package/corpus/toolkit/src/composer/pasted-text.ts +160 -0
  232. package/corpus/toolkit/src/composer/prompt-attachments.ts +93 -0
  233. package/corpus/toolkit/src/composer/realtime-voice-audio-level.ts +64 -0
  234. package/corpus/toolkit/src/composer/realtime-voice-transcript.ts +134 -0
  235. package/corpus/toolkit/src/composer/runtime-adapters.tsx +343 -0
  236. package/corpus/toolkit/src/composer/types.ts +66 -0
  237. package/corpus/toolkit/src/composer/use-file-search.ts +64 -0
  238. package/corpus/toolkit/src/composer/use-mention-search.ts +90 -0
  239. package/corpus/toolkit/src/composer/use-skills.ts +45 -0
  240. package/corpus/toolkit/src/composer/useRealtimeVoiceMode.tsx +2117 -0
  241. package/corpus/toolkit/src/composer/useVoiceDictation.ts +1191 -0
  242. package/corpus/toolkit/src/context-ui/ContextMeter.tsx +101 -0
  243. package/corpus/toolkit/src/context-ui/ContextSegmentRow.tsx +184 -0
  244. package/corpus/toolkit/src/context-ui/ContextTreemap.tsx +125 -0
  245. package/corpus/toolkit/src/context-ui/ContextXRayPanel.tsx +377 -0
  246. package/corpus/toolkit/src/context-ui/format.ts +42 -0
  247. package/corpus/toolkit/src/context-ui/index.ts +15 -0
  248. package/corpus/toolkit/src/context-ui/types.ts +43 -0
  249. package/corpus/toolkit/src/design-tweaks/index.ts +1 -0
  250. package/corpus/toolkit/src/design-tweaks/visual-style-controls.tsx +1084 -0
  251. package/corpus/toolkit/src/editor/BubbleToolbar.tsx +253 -0
  252. package/corpus/toolkit/src/editor/CodeBlockNode.tsx +254 -0
  253. package/corpus/toolkit/src/editor/DragHandle.ts +1437 -0
  254. package/corpus/toolkit/src/editor/ImageExtension.ts +306 -0
  255. package/corpus/toolkit/src/editor/RegistryBlockContext.tsx +107 -0
  256. package/corpus/toolkit/src/editor/RegistryBlockNode.tsx +652 -0
  257. package/corpus/toolkit/src/editor/RichMarkdownEditor.tsx +81 -0
  258. package/corpus/toolkit/src/editor/RunId.ts +61 -0
  259. package/corpus/toolkit/src/editor/SharedRichEditor.tsx +321 -0
  260. package/corpus/toolkit/src/editor/SlashCommandMenu.tsx +310 -0
  261. package/corpus/toolkit/src/editor/extensions.ts +402 -0
  262. package/corpus/toolkit/src/editor/gfmDoc.ts +92 -0
  263. package/corpus/toolkit/src/editor/index.ts +93 -0
  264. package/corpus/toolkit/src/editor/registrySlashCommands.ts +131 -0
  265. package/corpus/toolkit/src/editor/surgical-apply.ts +192 -0
  266. package/corpus/toolkit/src/editor/useCollabReconcile.ts +589 -0
  267. package/corpus/toolkit/src/editor.css +510 -0
  268. package/corpus/toolkit/src/hooks/index.ts +2 -0
  269. package/corpus/toolkit/src/hooks/use-mobile.tsx +21 -0
  270. package/corpus/toolkit/src/hooks/use-toast.ts +188 -0
  271. package/corpus/toolkit/src/index.ts +8 -0
  272. package/corpus/toolkit/src/onboarding/index.ts +1 -0
  273. package/corpus/toolkit/src/provider.tsx +47 -0
  274. package/corpus/toolkit/src/sharing/VisibilityBadge.tsx +37 -0
  275. package/corpus/toolkit/src/sharing/index.ts +4 -0
  276. package/corpus/toolkit/src/styles.css +21 -0
  277. package/corpus/toolkit/src/ui/accordion.tsx +56 -0
  278. package/corpus/toolkit/src/ui/alert-dialog.tsx +141 -0
  279. package/corpus/toolkit/src/ui/alert.tsx +59 -0
  280. package/corpus/toolkit/src/ui/aspect-ratio.tsx +5 -0
  281. package/corpus/toolkit/src/ui/avatar.tsx +48 -0
  282. package/corpus/toolkit/src/ui/badge.tsx +37 -0
  283. package/corpus/toolkit/src/ui/breadcrumb.tsx +115 -0
  284. package/corpus/toolkit/src/ui/button.tsx +78 -0
  285. package/corpus/toolkit/src/ui/calendar.tsx +214 -0
  286. package/corpus/toolkit/src/ui/card.tsx +86 -0
  287. package/corpus/toolkit/src/ui/carousel.tsx +260 -0
  288. package/corpus/toolkit/src/ui/chart.tsx +375 -0
  289. package/corpus/toolkit/src/ui/checkbox.tsx +28 -0
  290. package/corpus/toolkit/src/ui/collapsible.tsx +9 -0
  291. package/corpus/toolkit/src/ui/command.tsx +174 -0
  292. package/corpus/toolkit/src/ui/context-menu.tsx +202 -0
  293. package/corpus/toolkit/src/ui/date-picker.tsx +64 -0
  294. package/corpus/toolkit/src/ui/dialog.tsx +146 -0
  295. package/corpus/toolkit/src/ui/drawer.tsx +126 -0
  296. package/corpus/toolkit/src/ui/dropdown-menu.tsx +218 -0
  297. package/corpus/toolkit/src/ui/form.tsx +177 -0
  298. package/corpus/toolkit/src/ui/hover-card.tsx +27 -0
  299. package/corpus/toolkit/src/ui/index.ts +52 -0
  300. package/corpus/toolkit/src/ui/input-otp.tsx +69 -0
  301. package/corpus/toolkit/src/ui/input.tsx +22 -0
  302. package/corpus/toolkit/src/ui/label.tsx +24 -0
  303. package/corpus/toolkit/src/ui/logo.tsx +36 -0
  304. package/corpus/toolkit/src/ui/menubar.tsx +235 -0
  305. package/corpus/toolkit/src/ui/navigation-menu.tsx +128 -0
  306. package/corpus/toolkit/src/ui/pagination.tsx +121 -0
  307. package/corpus/toolkit/src/ui/popover.tsx +44 -0
  308. package/corpus/toolkit/src/ui/progress.tsx +26 -0
  309. package/corpus/toolkit/src/ui/radio-group.tsx +42 -0
  310. package/corpus/toolkit/src/ui/resizable.tsx +43 -0
  311. package/corpus/toolkit/src/ui/scroll-area.tsx +46 -0
  312. package/corpus/toolkit/src/ui/select.tsx +158 -0
  313. package/corpus/toolkit/src/ui/separator.tsx +29 -0
  314. package/corpus/toolkit/src/ui/sheet.tsx +160 -0
  315. package/corpus/toolkit/src/ui/sidebar.tsx +777 -0
  316. package/corpus/toolkit/src/ui/skeleton.tsx +18 -0
  317. package/corpus/toolkit/src/ui/slider.tsx +26 -0
  318. package/corpus/toolkit/src/ui/sonner.tsx +58 -0
  319. package/corpus/toolkit/src/ui/spinner.tsx +17 -0
  320. package/corpus/toolkit/src/ui/switch.tsx +27 -0
  321. package/corpus/toolkit/src/ui/table.tsx +117 -0
  322. package/corpus/toolkit/src/ui/tabs.tsx +53 -0
  323. package/corpus/toolkit/src/ui/textarea.tsx +23 -0
  324. package/corpus/toolkit/src/ui/toast.tsx +127 -0
  325. package/corpus/toolkit/src/ui/toaster.tsx +33 -0
  326. package/corpus/toolkit/src/ui/toggle-group.tsx +59 -0
  327. package/corpus/toolkit/src/ui/toggle.tsx +43 -0
  328. package/corpus/toolkit/src/ui/tooltip.tsx +53 -0
  329. package/corpus/toolkit/src/ui/use-toast.ts +3 -0
  330. package/corpus/toolkit/src/utils.ts +8 -0
  331. package/corpus/toolkit/tsconfig.json +23 -0
  332. package/dist/cli/atomic-json-file.d.ts +30 -0
  333. package/dist/cli/atomic-json-file.d.ts.map +1 -0
  334. package/dist/cli/atomic-json-file.js +240 -0
  335. package/dist/cli/atomic-json-file.js.map +1 -0
  336. package/dist/cli/claude-code-participant.d.ts +77 -0
  337. package/dist/cli/claude-code-participant.d.ts.map +1 -0
  338. package/dist/cli/claude-code-participant.js +355 -0
  339. package/dist/cli/claude-code-participant.js.map +1 -0
  340. package/dist/cli/code-agent-executor.d.ts.map +1 -1
  341. package/dist/cli/code-agent-executor.js +18 -10
  342. package/dist/cli/code-agent-executor.js.map +1 -1
  343. package/dist/cli/code-agent-runs.concurrent-worker.d.ts +2 -0
  344. package/dist/cli/code-agent-runs.concurrent-worker.d.ts.map +1 -0
  345. package/dist/cli/code-agent-runs.concurrent-worker.js +32 -0
  346. package/dist/cli/code-agent-runs.concurrent-worker.js.map +1 -0
  347. package/dist/cli/code-agent-runs.d.ts.map +1 -1
  348. package/dist/cli/code-agent-runs.js +43 -56
  349. package/dist/cli/code-agent-runs.js.map +1 -1
  350. package/dist/cli/codex-cli-participant.d.ts +77 -0
  351. package/dist/cli/codex-cli-participant.d.ts.map +1 -0
  352. package/dist/cli/codex-cli-participant.js +354 -0
  353. package/dist/cli/codex-cli-participant.js.map +1 -0
  354. package/dist/cli/multi-frontier-runs.d.ts +115 -0
  355. package/dist/cli/multi-frontier-runs.d.ts.map +1 -1
  356. package/dist/cli/multi-frontier-runs.js +831 -113
  357. package/dist/cli/multi-frontier-runs.js.map +1 -1
  358. package/dist/client/chat/ChatHistoryList.d.ts +1 -87
  359. package/dist/client/chat/ChatHistoryList.d.ts.map +1 -1
  360. package/dist/client/chat/ChatHistoryList.js +1 -82
  361. package/dist/client/chat/ChatHistoryList.js.map +1 -1
  362. package/dist/integrations/index.d.ts +2 -1
  363. package/dist/integrations/index.d.ts.map +1 -1
  364. package/dist/integrations/index.js +2 -1
  365. package/dist/integrations/index.js.map +1 -1
  366. package/dist/integrations/slack-manifest.d.ts +1 -1
  367. package/dist/integrations/slack-oauth.d.ts +1 -1
  368. package/dist/integrations/slack-oauth.d.ts.map +1 -1
  369. package/dist/integrations/slack-oauth.js +1 -0
  370. package/dist/integrations/slack-oauth.js.map +1 -1
  371. package/dist/integrations/webhook-delivery.d.ts +23 -0
  372. package/dist/integrations/webhook-delivery.d.ts.map +1 -0
  373. package/dist/integrations/webhook-delivery.js +32 -0
  374. package/dist/integrations/webhook-delivery.js.map +1 -0
  375. package/dist/notifications/channels.d.ts.map +1 -1
  376. package/dist/notifications/channels.js +4 -9
  377. package/dist/notifications/channels.js.map +1 -1
  378. package/dist/notifications/routes.d.ts +3 -3
  379. package/dist/provider-api/actions/custom-provider-registration.d.ts +136 -0
  380. package/dist/provider-api/actions/custom-provider-registration.d.ts.map +1 -0
  381. package/dist/provider-api/actions/custom-provider-registration.js +154 -0
  382. package/dist/provider-api/actions/custom-provider-registration.js.map +1 -0
  383. package/dist/provider-api/actions/delete-staged-dataset.d.ts +1 -1
  384. package/dist/provider-api/actions/delete-staged-dataset.d.ts.map +1 -1
  385. package/dist/provider-api/actions/delete-staged-dataset.js +3 -34
  386. package/dist/provider-api/actions/delete-staged-dataset.js.map +1 -1
  387. package/dist/provider-api/actions/list-staged-datasets.d.ts +2 -3
  388. package/dist/provider-api/actions/list-staged-datasets.d.ts.map +1 -1
  389. package/dist/provider-api/actions/list-staged-datasets.js +3 -40
  390. package/dist/provider-api/actions/list-staged-datasets.js.map +1 -1
  391. package/dist/provider-api/actions/provider-api-audit.d.ts +7 -0
  392. package/dist/provider-api/actions/provider-api-audit.d.ts.map +1 -0
  393. package/dist/provider-api/actions/provider-api-audit.js +74 -0
  394. package/dist/provider-api/actions/provider-api-audit.js.map +1 -0
  395. package/dist/provider-api/actions/provider-api.d.ts +370 -0
  396. package/dist/provider-api/actions/provider-api.d.ts.map +1 -0
  397. package/dist/provider-api/actions/provider-api.js +280 -0
  398. package/dist/provider-api/actions/provider-api.js.map +1 -0
  399. package/dist/provider-api/actions/query-staged-dataset.d.ts +1 -1
  400. package/dist/provider-api/actions/query-staged-dataset.d.ts.map +1 -1
  401. package/dist/provider-api/actions/query-staged-dataset.js +3 -116
  402. package/dist/provider-api/actions/query-staged-dataset.js.map +1 -1
  403. package/dist/provider-api/actions/staged-datasets.d.ts +190 -0
  404. package/dist/provider-api/actions/staged-datasets.d.ts.map +1 -0
  405. package/dist/provider-api/actions/staged-datasets.js +210 -0
  406. package/dist/provider-api/actions/staged-datasets.js.map +1 -0
  407. package/dist/provider-api/gong.d.ts +44 -0
  408. package/dist/provider-api/gong.d.ts.map +1 -0
  409. package/dist/provider-api/gong.js +56 -0
  410. package/dist/provider-api/gong.js.map +1 -0
  411. package/dist/scripts/docs/source-search.d.ts +2 -1
  412. package/dist/scripts/docs/source-search.d.ts.map +1 -1
  413. package/dist/scripts/docs/source-search.js +2 -1
  414. package/dist/scripts/docs/source-search.js.map +1 -1
  415. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  416. package/dist/server/google-oauth-credentials.d.ts +35 -0
  417. package/dist/server/google-oauth-credentials.d.ts.map +1 -1
  418. package/dist/server/google-oauth-credentials.js +48 -2
  419. package/dist/server/google-oauth-credentials.js.map +1 -1
  420. package/dist/server/index.d.ts +1 -1
  421. package/dist/server/index.d.ts.map +1 -1
  422. package/dist/server/index.js +1 -1
  423. package/dist/server/index.js.map +1 -1
  424. package/dist/styles/chat-history-list.css +1 -313
  425. package/dist/templates/chat/.agents/skills/actions/SKILL.md +32 -12
  426. package/dist/templates/chat/.agents/skills/agent-native-docs/SKILL.md +10 -0
  427. package/dist/templates/chat/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  428. package/dist/templates/chat/.agents/skills/self-modifying-code/SKILL.md +11 -2
  429. package/dist/templates/chat/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  430. package/dist/templates/chat/AGENTS.md +5 -2
  431. package/dist/templates/chat/app/components/layout/Sidebar.tsx +51 -157
  432. package/dist/templates/default/.agents/skills/actions/SKILL.md +32 -12
  433. package/dist/templates/default/.agents/skills/agent-native-docs/SKILL.md +10 -0
  434. package/dist/templates/default/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  435. package/dist/templates/default/.agents/skills/integration-webhooks/SKILL.md +323 -0
  436. package/dist/templates/default/.agents/skills/onboarding/SKILL.md +56 -0
  437. package/dist/templates/default/.agents/skills/secrets/SKILL.md +273 -0
  438. package/dist/templates/default/.agents/skills/self-modifying-code/SKILL.md +11 -2
  439. package/dist/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  440. package/dist/templates/default/AGENTS.md +25 -18
  441. package/dist/templates/headless/.agents/skills/actions/SKILL.md +512 -0
  442. package/dist/templates/headless/.agents/skills/agent-native-docs/SKILL.md +10 -0
  443. package/dist/templates/headless/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  444. package/dist/templates/headless/.agents/skills/integration-webhooks/SKILL.md +323 -0
  445. package/dist/templates/headless/.agents/skills/secrets/SKILL.md +273 -0
  446. package/dist/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  447. package/dist/templates/headless/AGENTS.md +7 -0
  448. package/dist/templates/workspace-core/.agents/skills/actions/SKILL.md +32 -12
  449. package/dist/templates/workspace-core/.agents/skills/agent-native-docs/SKILL.md +10 -0
  450. package/dist/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
  451. package/dist/templates/workspace-core/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  452. package/dist/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +11 -2
  453. package/dist/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  454. package/dist/templates/workspace-core/AGENTS.md +4 -0
  455. package/dist/templates/workspace-root/AGENTS.md +4 -0
  456. package/docs/AGENTS.md +7 -0
  457. package/docs/content/integrations.mdx +63 -2
  458. package/docs/design/provider-integration-authority.md +117 -0
  459. package/package.json +7 -2
  460. package/src/templates/chat/.agents/skills/actions/SKILL.md +32 -12
  461. package/src/templates/chat/.agents/skills/agent-native-docs/SKILL.md +10 -0
  462. package/src/templates/chat/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  463. package/src/templates/chat/.agents/skills/self-modifying-code/SKILL.md +11 -2
  464. package/src/templates/chat/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  465. package/src/templates/chat/AGENTS.md +5 -2
  466. package/src/templates/chat/app/components/layout/Sidebar.tsx +51 -157
  467. package/src/templates/default/.agents/skills/actions/SKILL.md +32 -12
  468. package/src/templates/default/.agents/skills/agent-native-docs/SKILL.md +10 -0
  469. package/src/templates/default/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  470. package/src/templates/default/.agents/skills/integration-webhooks/SKILL.md +323 -0
  471. package/src/templates/default/.agents/skills/onboarding/SKILL.md +56 -0
  472. package/src/templates/default/.agents/skills/secrets/SKILL.md +273 -0
  473. package/src/templates/default/.agents/skills/self-modifying-code/SKILL.md +11 -2
  474. package/src/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  475. package/src/templates/default/AGENTS.md +25 -18
  476. package/src/templates/headless/.agents/skills/actions/SKILL.md +512 -0
  477. package/src/templates/headless/.agents/skills/agent-native-docs/SKILL.md +10 -0
  478. package/src/templates/headless/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  479. package/src/templates/headless/.agents/skills/integration-webhooks/SKILL.md +323 -0
  480. package/src/templates/headless/.agents/skills/secrets/SKILL.md +273 -0
  481. package/src/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  482. package/src/templates/headless/AGENTS.md +7 -0
  483. package/src/templates/workspace-core/.agents/skills/actions/SKILL.md +32 -12
  484. package/src/templates/workspace-core/.agents/skills/agent-native-docs/SKILL.md +10 -0
  485. package/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
  486. package/src/templates/workspace-core/.agents/skills/customizing-agent-native/SKILL.md +102 -0
  487. package/src/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +11 -2
  488. package/src/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +6 -0
  489. package/src/templates/workspace-core/AGENTS.md +4 -0
  490. package/src/templates/workspace-root/AGENTS.md +4 -0
@@ -0,0 +1,1084 @@
1
+ import { IconColorPicker } from "@tabler/icons-react";
2
+ import {
3
+ useEffect,
4
+ useId,
5
+ useRef,
6
+ useState,
7
+ type KeyboardEvent,
8
+ type PointerEvent,
9
+ type ReactNode,
10
+ } from "react";
11
+
12
+ import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover.js";
13
+ import { cn } from "../utils.js";
14
+
15
+ export type VisualControlValue = string | number | boolean;
16
+
17
+ export interface VisualControlOption {
18
+ label: string;
19
+ value: string;
20
+ color?: string;
21
+ }
22
+
23
+ export interface VisualTweakDefinition {
24
+ id: string;
25
+ label: string;
26
+ type: "color-swatch" | "color-swatches" | "segment" | "slider" | "toggle";
27
+ options?: VisualControlOption[];
28
+ min?: number;
29
+ max?: number;
30
+ step?: number;
31
+ defaultValue: VisualControlValue;
32
+ cssVar?: string;
33
+ unit?: string;
34
+ }
35
+
36
+ function clampNumber(value: number, min?: number, max?: number) {
37
+ let next = value;
38
+ if (typeof min === "number") next = Math.max(min, next);
39
+ if (typeof max === "number") next = Math.min(max, next);
40
+ return next;
41
+ }
42
+
43
+ function formatNumber(value: number, unit?: string) {
44
+ const rounded = Number.isInteger(value) ? value : Number(value.toFixed(2));
45
+ return unit ? `${rounded}${unit}` : String(rounded);
46
+ }
47
+
48
+ function parseDraftNumber(value: string, fallback: number) {
49
+ const match = value.trim().match(/-?\d+(?:\.\d+)?/);
50
+ if (!match) return fallback;
51
+ const next = Number(match[0]);
52
+ return Number.isFinite(next) ? next : fallback;
53
+ }
54
+
55
+ interface RgbaColor {
56
+ r: number;
57
+ g: number;
58
+ b: number;
59
+ a: number;
60
+ }
61
+
62
+ interface HsvaColor {
63
+ h: number;
64
+ s: number;
65
+ v: number;
66
+ a: number;
67
+ }
68
+
69
+ const CHECKERBOARD_IMAGE =
70
+ "linear-gradient(45deg, #d4d4d4 25%, transparent 25%), linear-gradient(-45deg, #d4d4d4 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #d4d4d4 75%), linear-gradient(-45deg, transparent 75%, #d4d4d4 75%)";
71
+ const FALLBACK_RGBA: RgbaColor = { r: 0, g: 0, b: 0, a: 1 };
72
+
73
+ function normalizeRgba(color: RgbaColor): RgbaColor {
74
+ return {
75
+ r: clamp(color.r, 0, 255),
76
+ g: clamp(color.g, 0, 255),
77
+ b: clamp(color.b, 0, 255),
78
+ a: clampFloat(color.a, 0, 1),
79
+ };
80
+ }
81
+
82
+ function normalizeHexColor(value: string): string | null {
83
+ const trimmed = value.trim();
84
+ if (/^#[0-9a-f]{6}$/i.test(trimmed)) return trimmed.toUpperCase();
85
+ if (/^[0-9a-f]{6}$/i.test(trimmed)) return `#${trimmed}`.toUpperCase();
86
+ if (/^#[0-9a-f]{3}$/i.test(trimmed)) {
87
+ return `#${trimmed
88
+ .slice(1)
89
+ .split("")
90
+ .map((char) => char + char)
91
+ .join("")}`.toUpperCase();
92
+ }
93
+ if (/^[0-9a-f]{3}$/i.test(trimmed)) {
94
+ return `#${trimmed
95
+ .split("")
96
+ .map((char) => char + char)
97
+ .join("")}`.toUpperCase();
98
+ }
99
+ return null;
100
+ }
101
+
102
+ function parseCssColor(value: string): RgbaColor | null {
103
+ const trimmed = value.trim();
104
+ if (!trimmed) return null;
105
+ const normalizedHex = normalizeHexColor(trimmed);
106
+ if (normalizedHex) {
107
+ return normalizeRgba({
108
+ r: Number.parseInt(normalizedHex.slice(1, 3), 16),
109
+ g: Number.parseInt(normalizedHex.slice(3, 5), 16),
110
+ b: Number.parseInt(normalizedHex.slice(5, 7), 16),
111
+ a: 1,
112
+ });
113
+ }
114
+ const rgb = trimmed.match(
115
+ /^rgba?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)(?:\s*,\s*([0-9.]+%?))?/i,
116
+ );
117
+ if (rgb) {
118
+ return normalizeRgba({
119
+ r: Number(rgb[1]),
120
+ g: Number(rgb[2]),
121
+ b: Number(rgb[3]),
122
+ a: parseAlpha(rgb[4]),
123
+ });
124
+ }
125
+ const lower = trimmed.toLowerCase();
126
+ if (lower === "transparent" || lower === "rgba(0, 0, 0, 0)") {
127
+ return { r: 0, g: 0, b: 0, a: 0 };
128
+ }
129
+ return null;
130
+ }
131
+
132
+ function channelToHex(value: number): string {
133
+ return clamp(value, 0, 255).toString(16).padStart(2, "0");
134
+ }
135
+
136
+ function rgbaToHex(color: RgbaColor): string {
137
+ const normalized = normalizeRgba(color);
138
+ return `#${channelToHex(normalized.r)}${channelToHex(normalized.g)}${channelToHex(normalized.b)}`.toUpperCase();
139
+ }
140
+
141
+ function rgbaToCss(color: RgbaColor): string {
142
+ const normalized = normalizeRgba(color);
143
+ if (normalized.a >= 1) return rgbaToHex(normalized);
144
+ return `rgba(${normalized.r}, ${normalized.g}, ${normalized.b}, ${trimNumber(normalized.a)})`;
145
+ }
146
+
147
+ function parseAlpha(value: string | undefined): number {
148
+ if (!value) return 1;
149
+ if (value.endsWith("%")) return Number(value.slice(0, -1)) / 100;
150
+ return Number(value);
151
+ }
152
+
153
+ function trimNumber(value: number) {
154
+ return Number(value.toFixed(2)).toString();
155
+ }
156
+
157
+ function rgbaToHsv(color: RgbaColor): HsvaColor {
158
+ const normalized = normalizeRgba(color);
159
+ const r = normalized.r / 255;
160
+ const g = normalized.g / 255;
161
+ const b = normalized.b / 255;
162
+ const max = Math.max(r, g, b);
163
+ const min = Math.min(r, g, b);
164
+ const delta = max - min;
165
+
166
+ let h = 0;
167
+ if (delta !== 0) {
168
+ if (max === r) h = ((g - b) / delta) % 6;
169
+ else if (max === g) h = (b - r) / delta + 2;
170
+ else h = (r - g) / delta + 4;
171
+ h *= 60;
172
+ if (h < 0) h += 360;
173
+ }
174
+
175
+ return {
176
+ h: Math.round(h),
177
+ s: max === 0 ? 0 : Math.round((delta / max) * 100),
178
+ v: Math.round(max * 100),
179
+ a: normalized.a,
180
+ };
181
+ }
182
+
183
+ function hsvToRgba(color: HsvaColor): RgbaColor {
184
+ const h = ((color.h % 360) + 360) % 360;
185
+ const s = clampFloat(color.s, 0, 100) / 100;
186
+ const v = clampFloat(color.v, 0, 100) / 100;
187
+ const chroma = v * s;
188
+ const x = chroma * (1 - Math.abs(((h / 60) % 2) - 1));
189
+ const m = v - chroma;
190
+
191
+ let r = 0;
192
+ let g = 0;
193
+ let b = 0;
194
+ if (h < 60) [r, g, b] = [chroma, x, 0];
195
+ else if (h < 120) [r, g, b] = [x, chroma, 0];
196
+ else if (h < 180) [r, g, b] = [0, chroma, x];
197
+ else if (h < 240) [r, g, b] = [0, x, chroma];
198
+ else if (h < 300) [r, g, b] = [x, 0, chroma];
199
+ else [r, g, b] = [chroma, 0, x];
200
+
201
+ return normalizeRgba({
202
+ r: Math.round((r + m) * 255),
203
+ g: Math.round((g + m) * 255),
204
+ b: Math.round((b + m) * 255),
205
+ a: clampFloat(color.a, 0, 1),
206
+ });
207
+ }
208
+
209
+ function clamp(value: number, min: number, max: number): number {
210
+ if (!Number.isFinite(value)) return min;
211
+ return Math.max(min, Math.min(max, Math.round(value)));
212
+ }
213
+
214
+ function clampFloat(value: number, min: number, max: number): number {
215
+ if (!Number.isFinite(value)) return min;
216
+ return Math.max(min, Math.min(max, value));
217
+ }
218
+
219
+ function swatchBackground(value: string) {
220
+ if (value === "transparent" || value === "rgba(0, 0, 0, 0)") {
221
+ return {
222
+ background: CHECKERBOARD_IMAGE,
223
+ backgroundPosition: "0 0, 0 4px, 4px -4px, -4px 0",
224
+ backgroundSize: "8px 8px",
225
+ };
226
+ }
227
+ return { background: value };
228
+ }
229
+
230
+ export function VisualInspectorPanel({
231
+ title,
232
+ subtitle,
233
+ children,
234
+ className,
235
+ headerAction,
236
+ }: {
237
+ title: ReactNode;
238
+ subtitle?: ReactNode;
239
+ children: ReactNode;
240
+ className?: string;
241
+ headerAction?: ReactNode;
242
+ }) {
243
+ return (
244
+ <aside
245
+ className={cn(
246
+ "w-64 overflow-hidden rounded-xl border border-border bg-card/95 text-card-foreground shadow-2xl shadow-black/35 backdrop-blur",
247
+ className,
248
+ )}
249
+ >
250
+ <div className="flex min-h-10 items-start justify-between gap-2 border-b border-border/70 px-3 py-2.5">
251
+ <div className="min-w-0">
252
+ <div className="truncate text-[11px] font-semibold uppercase tracking-wide text-muted-foreground">
253
+ {title}
254
+ </div>
255
+ {subtitle && (
256
+ <div className="mt-0.5 truncate text-[12px] text-foreground">
257
+ {subtitle}
258
+ </div>
259
+ )}
260
+ </div>
261
+ {headerAction}
262
+ </div>
263
+ <div className="max-h-[min(680px,calc(100vh-7rem))] overflow-y-auto p-2">
264
+ {children}
265
+ </div>
266
+ </aside>
267
+ );
268
+ }
269
+
270
+ export function VisualInspectorSection({
271
+ title,
272
+ children,
273
+ className,
274
+ }: {
275
+ title: ReactNode;
276
+ children: ReactNode;
277
+ className?: string;
278
+ }) {
279
+ return (
280
+ <section className={cn("rounded-lg px-1.5 py-2", className)}>
281
+ <div className="mb-2 px-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground/75">
282
+ {title}
283
+ </div>
284
+ <div className="space-y-2">{children}</div>
285
+ </section>
286
+ );
287
+ }
288
+
289
+ export function VisualControlRow({
290
+ label,
291
+ children,
292
+ className,
293
+ }: {
294
+ label: ReactNode;
295
+ children: ReactNode;
296
+ className?: string;
297
+ }) {
298
+ return (
299
+ <div className={cn("grid gap-1", className)}>
300
+ <span className="text-[11px] text-muted-foreground">{label}</span>
301
+ {children}
302
+ </div>
303
+ );
304
+ }
305
+
306
+ export function VisualSwatchControl({
307
+ options,
308
+ value,
309
+ onChange,
310
+ columns = 8,
311
+ className,
312
+ }: {
313
+ options: VisualControlOption[];
314
+ value: string;
315
+ onChange: (value: string) => void;
316
+ columns?: number;
317
+ className?: string;
318
+ }) {
319
+ return (
320
+ <div
321
+ className={cn("grid gap-1.5", className)}
322
+ style={{ gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))` }}
323
+ >
324
+ {options.map((option) => {
325
+ const swatch = option.color ?? option.value;
326
+ const isTransparent = swatch === "transparent";
327
+ return (
328
+ <button
329
+ key={`${option.value}-${option.label}`}
330
+ type="button"
331
+ title={option.label}
332
+ aria-label={option.label}
333
+ onClick={() => onChange(option.value)}
334
+ className={cn(
335
+ "size-5 cursor-pointer rounded-md border border-border/70 transition-[transform,border-color] hover:scale-105 hover:border-foreground/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
336
+ value === option.value &&
337
+ "border-foreground/70 ring-2 ring-foreground/40 ring-offset-1 ring-offset-card",
338
+ )}
339
+ style={{
340
+ background: isTransparent
341
+ ? "linear-gradient(45deg, hsl(var(--muted)) 25%, transparent 25%), linear-gradient(-45deg, hsl(var(--muted)) 25%, transparent 25%), linear-gradient(45deg, transparent 75%, hsl(var(--muted)) 75%), linear-gradient(-45deg, transparent 75%, hsl(var(--muted)) 75%)"
342
+ : swatch,
343
+ backgroundPosition: isTransparent
344
+ ? "0 0, 0 4px, 4px -4px, -4px 0"
345
+ : undefined,
346
+ backgroundSize: isTransparent ? "8px 8px" : undefined,
347
+ }}
348
+ />
349
+ );
350
+ })}
351
+ </div>
352
+ );
353
+ }
354
+
355
+ export function VisualColorPicker({
356
+ label,
357
+ value,
358
+ onChange,
359
+ documentColors = [],
360
+ allowTransparent = false,
361
+ hexLabel = "Hex",
362
+ documentColorsLabel = "Document colors",
363
+ transparentLabel = "Transparent",
364
+ className,
365
+ }: {
366
+ label: string;
367
+ value: string;
368
+ onChange: (value: string) => void;
369
+ documentColors?: string[];
370
+ allowTransparent?: boolean;
371
+ hexLabel?: string;
372
+ documentColorsLabel?: string;
373
+ transparentLabel?: string;
374
+ className?: string;
375
+ }) {
376
+ const [open, setOpen] = useState(false);
377
+ const color = parseCssColor(value) ?? FALLBACK_RGBA;
378
+ const colorHex = rgbaToHex(color);
379
+ const [draft, setDraft] = useState(colorHex.replace(/^#/, ""));
380
+ const [picking, setPicking] = useState(false);
381
+ const hasEyeDropper = typeof window !== "undefined" && "EyeDropper" in window;
382
+
383
+ useEffect(() => {
384
+ if (!open) setDraft(colorHex.replace(/^#/, ""));
385
+ }, [colorHex, open]);
386
+
387
+ const commitDraft = (nextDraft = draft) => {
388
+ const normalized = normalizeHexColor(nextDraft);
389
+ if (!normalized) {
390
+ setDraft(colorHex.replace(/^#/, ""));
391
+ return;
392
+ }
393
+ setDraft(normalized.replace(/^#/, ""));
394
+ onChange(normalized);
395
+ };
396
+
397
+ const emitHsv = (nextHsv: HsvaColor) => {
398
+ const nextColor = hsvToRgba({ ...nextHsv, a: color.a });
399
+ const nextValue = rgbaToCss(nextColor);
400
+ setDraft(rgbaToHex(nextColor).replace(/^#/, ""));
401
+ onChange(nextValue);
402
+ };
403
+
404
+ const pickScreenColor = async () => {
405
+ if (!hasEyeDropper || picking) return;
406
+ try {
407
+ setPicking(true);
408
+ const EyeDropperCtor = (
409
+ window as unknown as {
410
+ EyeDropper?: new () => { open: () => Promise<{ sRGBHex: string }> };
411
+ }
412
+ ).EyeDropper;
413
+ if (!EyeDropperCtor) return;
414
+ const result = await new EyeDropperCtor().open();
415
+ if (result?.sRGBHex) {
416
+ const next = normalizeHexColor(result.sRGBHex);
417
+ if (next) {
418
+ setDraft(next.replace(/^#/, ""));
419
+ onChange(next);
420
+ }
421
+ }
422
+ } finally {
423
+ setPicking(false);
424
+ }
425
+ };
426
+
427
+ const uniqueDocumentColors = Array.from(
428
+ new Set(
429
+ documentColors
430
+ .map((color) =>
431
+ color === "transparent" ? color : normalizeHexColor(color),
432
+ )
433
+ .filter(Boolean) as string[],
434
+ ),
435
+ ).slice(0, 16);
436
+ const displayValue =
437
+ value === "transparent" || color.a === 0
438
+ ? transparentLabel
439
+ : colorHex.replace(/^#/, "");
440
+ const hsv = rgbaToHsv(color);
441
+
442
+ return (
443
+ <Popover open={open} onOpenChange={setOpen}>
444
+ <PopoverTrigger asChild>
445
+ <button
446
+ type="button"
447
+ aria-label={label}
448
+ className={cn(
449
+ "flex h-7 w-full cursor-pointer items-center gap-1.5 rounded-md border border-border bg-background/70 px-2 text-[11px] shadow-none transition-colors hover:bg-accent/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
450
+ className,
451
+ )}
452
+ >
453
+ <span
454
+ className="size-4 shrink-0 rounded-[3px] border border-border/70"
455
+ style={swatchBackground(value)}
456
+ />
457
+ <span className="min-w-0 flex-1 truncate text-left font-medium tabular-nums text-foreground">
458
+ {displayValue}
459
+ </span>
460
+ <span
461
+ aria-hidden="true"
462
+ className="size-0 border-x-[4px] border-t-[5px] border-x-transparent border-t-muted-foreground/70"
463
+ />
464
+ </button>
465
+ </PopoverTrigger>
466
+ <PopoverContent
467
+ side="left"
468
+ align="start"
469
+ sideOffset={8}
470
+ className="z-[10000] w-[252px] p-0 text-[11px] shadow-xl"
471
+ onFocusOutside={(event) => event.preventDefault()}
472
+ >
473
+ <div className="rounded-md bg-popover text-popover-foreground">
474
+ <VisualSaturationBrightnessField
475
+ hsv={hsv}
476
+ label={label}
477
+ onChange={emitHsv}
478
+ />
479
+
480
+ <div className="mt-2.5 px-3">
481
+ <div className="grid grid-cols-[1.5rem_1fr] items-center gap-x-2">
482
+ <div className="flex items-center justify-center">
483
+ <button
484
+ type="button"
485
+ aria-label={label}
486
+ disabled={!hasEyeDropper || picking}
487
+ onClick={() => void pickScreenColor()}
488
+ className={cn(
489
+ "flex size-6 cursor-pointer items-center justify-center rounded-sm text-muted-foreground transition-colors hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
490
+ (!hasEyeDropper || picking) &&
491
+ "pointer-events-none opacity-40",
492
+ picking &&
493
+ "bg-primary/10 text-primary ring-1 ring-primary/50",
494
+ )}
495
+ >
496
+ <IconColorPicker className="size-4" />
497
+ </button>
498
+ </div>
499
+ <VisualColorTrack
500
+ label={label}
501
+ value={hsv.h}
502
+ min={0}
503
+ max={360}
504
+ backgroundImage="linear-gradient(90deg, #ff0000, #ffff00, #00ff00, #00ffff, #0000ff, #ff00ff, #ff0000)"
505
+ onChange={(nextHue) =>
506
+ emitHsv({ ...hsv, h: nextHue === 360 ? 0 : nextHue })
507
+ }
508
+ />
509
+ <span
510
+ className="mt-2 size-[18px] shrink-0 rounded-[3px] border border-border/60"
511
+ style={swatchBackground(rgbaToCss(color))}
512
+ />
513
+ <VisualColorTrack
514
+ label={transparentLabel}
515
+ value={Math.round(color.a * 100)}
516
+ min={0}
517
+ max={100}
518
+ backgroundImage={`${CHECKERBOARD_IMAGE}, linear-gradient(90deg, rgba(${color.r}, ${color.g}, ${color.b}, 0), rgba(${color.r}, ${color.g}, ${color.b}, 1))`}
519
+ backgroundSize="8px 8px, 8px 8px, 8px 8px, 8px 8px, 100% 100%"
520
+ backgroundPosition="0 0, 0 4px, 4px -4px, -4px 0, 0 0"
521
+ onChange={(nextOpacity) => {
522
+ const nextColor = normalizeRgba({
523
+ ...color,
524
+ a: nextOpacity / 100,
525
+ });
526
+ const nextValue = rgbaToCss(nextColor);
527
+ setDraft(rgbaToHex(nextColor).replace(/^#/, ""));
528
+ onChange(nextValue);
529
+ }}
530
+ />
531
+ </div>
532
+ </div>
533
+
534
+ <div className="mt-2.5 grid grid-cols-[4.5rem_1fr] items-center gap-1 border-b border-border/70 px-3 pb-3">
535
+ <span className="text-[11px] font-medium text-muted-foreground">
536
+ {hexLabel}
537
+ </span>
538
+ <input
539
+ value={draft}
540
+ spellCheck={false}
541
+ onChange={(event) => setDraft(event.currentTarget.value)}
542
+ onFocus={(event) => event.currentTarget.select()}
543
+ onBlur={() => commitDraft()}
544
+ onKeyDown={(event) => {
545
+ if (event.key === "Enter") {
546
+ event.preventDefault();
547
+ commitDraft();
548
+ event.currentTarget.blur();
549
+ }
550
+ if (event.key === "Escape") {
551
+ event.preventDefault();
552
+ setDraft(colorHex.replace(/^#/, ""));
553
+ event.currentTarget.blur();
554
+ }
555
+ }}
556
+ className="h-6 min-w-0 rounded-md border border-input bg-background/70 px-2 text-right text-[11px] uppercase tabular-nums outline-none transition-colors focus:border-ring focus:ring-1 focus:ring-ring"
557
+ />
558
+ </div>
559
+ {(allowTransparent || uniqueDocumentColors.length > 0) && (
560
+ <div className="px-3 py-2.5">
561
+ <div className="mb-2 flex h-5 items-center justify-between text-[11px] text-muted-foreground">
562
+ <span>{documentColorsLabel}</span>
563
+ </div>
564
+ <div className="grid grid-cols-8 gap-1">
565
+ {allowTransparent && (
566
+ <button
567
+ type="button"
568
+ aria-label={transparentLabel}
569
+ aria-pressed={value === "transparent"}
570
+ className={cn(
571
+ "size-5 cursor-pointer rounded-sm border transition-transform hover:scale-110 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
572
+ value === "transparent" || color.a === 0
573
+ ? "border-primary ring-1 ring-primary"
574
+ : "border-border/60",
575
+ )}
576
+ style={swatchBackground("transparent")}
577
+ onClick={() => {
578
+ setDraft(colorHex.replace(/^#/, ""));
579
+ onChange("transparent");
580
+ }}
581
+ />
582
+ )}
583
+ {uniqueDocumentColors.map((color) => (
584
+ <button
585
+ key={color}
586
+ type="button"
587
+ title={color}
588
+ aria-label={color}
589
+ aria-pressed={colorHex === color}
590
+ className={cn(
591
+ "size-5 cursor-pointer rounded-sm border transition-transform hover:scale-110 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
592
+ colorHex === color
593
+ ? "border-primary ring-1 ring-primary"
594
+ : "border-border/60",
595
+ )}
596
+ style={swatchBackground(color)}
597
+ onClick={() => {
598
+ setDraft(color);
599
+ onChange(color);
600
+ }}
601
+ />
602
+ ))}
603
+ </div>
604
+ </div>
605
+ )}
606
+ </div>
607
+ </PopoverContent>
608
+ </Popover>
609
+ );
610
+ }
611
+
612
+ function VisualSaturationBrightnessField({
613
+ hsv,
614
+ label,
615
+ onChange,
616
+ }: {
617
+ hsv: HsvaColor;
618
+ label: string;
619
+ onChange: (value: HsvaColor) => void;
620
+ }) {
621
+ const fieldRef = useRef<HTMLDivElement>(null);
622
+ const draggingRef = useRef(false);
623
+ const hueColor = rgbaToCss(hsvToRgba({ h: hsv.h, s: 100, v: 100, a: 1 }));
624
+
625
+ const updateFromPointer = (event: PointerEvent<HTMLDivElement>) => {
626
+ const rect = fieldRef.current?.getBoundingClientRect();
627
+ if (!rect) return;
628
+ onChange({
629
+ ...hsv,
630
+ s: clamp(((event.clientX - rect.left) / rect.width) * 100, 0, 100),
631
+ v: clamp(100 - ((event.clientY - rect.top) / rect.height) * 100, 0, 100),
632
+ });
633
+ };
634
+
635
+ const stepWithKeyboard = (event: KeyboardEvent<HTMLDivElement>) => {
636
+ const step = event.shiftKey ? 10 : 1;
637
+ if (event.key === "ArrowRight") {
638
+ event.preventDefault();
639
+ onChange({ ...hsv, s: clamp(hsv.s + step, 0, 100) });
640
+ }
641
+ if (event.key === "ArrowLeft") {
642
+ event.preventDefault();
643
+ onChange({ ...hsv, s: clamp(hsv.s - step, 0, 100) });
644
+ }
645
+ if (event.key === "ArrowUp") {
646
+ event.preventDefault();
647
+ onChange({ ...hsv, v: clamp(hsv.v + step, 0, 100) });
648
+ }
649
+ if (event.key === "ArrowDown") {
650
+ event.preventDefault();
651
+ onChange({ ...hsv, v: clamp(hsv.v - step, 0, 100) });
652
+ }
653
+ };
654
+
655
+ return (
656
+ <div
657
+ ref={fieldRef}
658
+ tabIndex={0}
659
+ aria-label={label}
660
+ onPointerDown={(event) => {
661
+ draggingRef.current = true;
662
+ event.currentTarget.setPointerCapture(event.pointerId);
663
+ updateFromPointer(event);
664
+ }}
665
+ onPointerMove={(event) => {
666
+ if (!draggingRef.current) return;
667
+ updateFromPointer(event);
668
+ }}
669
+ onPointerUp={(event) => {
670
+ draggingRef.current = false;
671
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) {
672
+ event.currentTarget.releasePointerCapture(event.pointerId);
673
+ }
674
+ }}
675
+ onPointerCancel={() => {
676
+ draggingRef.current = false;
677
+ }}
678
+ onKeyDown={stepWithKeyboard}
679
+ className="relative h-40 w-full cursor-crosshair overflow-hidden rounded-t-md outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset active:cursor-grabbing"
680
+ style={{
681
+ backgroundImage: `linear-gradient(to top, #000 0%, transparent 100%), linear-gradient(to right, #fff 0%, ${hueColor} 100%)`,
682
+ }}
683
+ >
684
+ <span
685
+ className="pointer-events-none absolute size-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white shadow-[0_0_0_1px_hsl(var(--foreground)/0.6)]"
686
+ style={{
687
+ left: `${hsv.s}%`,
688
+ top: `${100 - hsv.v}%`,
689
+ }}
690
+ />
691
+ </div>
692
+ );
693
+ }
694
+
695
+ function VisualColorTrack({
696
+ label,
697
+ value,
698
+ min,
699
+ max,
700
+ backgroundImage,
701
+ backgroundSize,
702
+ backgroundPosition,
703
+ onChange,
704
+ }: {
705
+ label: string;
706
+ value: number;
707
+ min: number;
708
+ max: number;
709
+ backgroundImage: string;
710
+ backgroundSize?: string;
711
+ backgroundPosition?: string;
712
+ onChange: (value: number) => void;
713
+ }) {
714
+ const trackRef = useRef<HTMLDivElement>(null);
715
+ const draggingRef = useRef(false);
716
+ const percent = ((value - min) / (max - min)) * 100;
717
+
718
+ const updateFromPointer = (event: PointerEvent<HTMLDivElement>) => {
719
+ const rect = trackRef.current?.getBoundingClientRect();
720
+ if (!rect) return;
721
+ const next = min + ((event.clientX - rect.left) / rect.width) * (max - min);
722
+ onChange(clamp(next, min, max));
723
+ };
724
+
725
+ const stepWithKeyboard = (event: KeyboardEvent<HTMLDivElement>) => {
726
+ const step = event.shiftKey ? 10 : 1;
727
+ if (event.key === "ArrowRight" || event.key === "ArrowUp") {
728
+ event.preventDefault();
729
+ onChange(clamp(value + step, min, max));
730
+ }
731
+ if (event.key === "ArrowLeft" || event.key === "ArrowDown") {
732
+ event.preventDefault();
733
+ onChange(clamp(value - step, min, max));
734
+ }
735
+ if (event.key === "Home") {
736
+ event.preventDefault();
737
+ onChange(min);
738
+ }
739
+ if (event.key === "End") {
740
+ event.preventDefault();
741
+ onChange(max);
742
+ }
743
+ };
744
+
745
+ return (
746
+ <div
747
+ ref={trackRef}
748
+ role="slider"
749
+ tabIndex={0}
750
+ aria-label={label}
751
+ aria-valuemin={min}
752
+ aria-valuemax={max}
753
+ aria-valuenow={Math.round(value)}
754
+ onKeyDown={stepWithKeyboard}
755
+ onPointerDown={(event) => {
756
+ draggingRef.current = true;
757
+ event.currentTarget.setPointerCapture(event.pointerId);
758
+ updateFromPointer(event);
759
+ }}
760
+ onPointerMove={(event) => {
761
+ if (!draggingRef.current) return;
762
+ updateFromPointer(event);
763
+ }}
764
+ onPointerUp={(event) => {
765
+ draggingRef.current = false;
766
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) {
767
+ event.currentTarget.releasePointerCapture(event.pointerId);
768
+ }
769
+ }}
770
+ onPointerCancel={() => {
771
+ draggingRef.current = false;
772
+ }}
773
+ className="relative h-3.5 cursor-pointer rounded-full border border-border/60 outline-none ring-offset-background focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 active:cursor-grabbing"
774
+ style={{ backgroundImage, backgroundSize, backgroundPosition }}
775
+ >
776
+ <span
777
+ className="pointer-events-none absolute top-1/2 size-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white shadow-[0_0_0_1px_hsl(var(--foreground)/0.6)]"
778
+ style={{ left: `${clamp(percent, 0, 100)}%` }}
779
+ />
780
+ </div>
781
+ );
782
+ }
783
+
784
+ export function VisualSegmentedControl({
785
+ options,
786
+ value,
787
+ onChange,
788
+ className,
789
+ }: {
790
+ options: VisualControlOption[];
791
+ value: string;
792
+ onChange: (value: string) => void;
793
+ className?: string;
794
+ }) {
795
+ return (
796
+ <div
797
+ className={cn(
798
+ "flex min-h-7 overflow-hidden rounded-md border border-border bg-background/60",
799
+ className,
800
+ )}
801
+ >
802
+ {options.map((option) => (
803
+ <button
804
+ key={option.value}
805
+ type="button"
806
+ onClick={() => onChange(option.value)}
807
+ className={cn(
808
+ "min-w-0 flex-1 cursor-pointer px-2 text-[11px] font-medium text-muted-foreground transition-colors hover:bg-accent/70 hover:text-foreground",
809
+ value === option.value && "bg-accent text-foreground",
810
+ )}
811
+ >
812
+ <span className="truncate">{option.label}</span>
813
+ </button>
814
+ ))}
815
+ </div>
816
+ );
817
+ }
818
+
819
+ export function VisualToggleControl({
820
+ checked,
821
+ onChange,
822
+ label,
823
+ }: {
824
+ checked: boolean;
825
+ onChange: (checked: boolean) => void;
826
+ label?: string;
827
+ }) {
828
+ return (
829
+ <button
830
+ type="button"
831
+ aria-pressed={checked}
832
+ aria-label={label}
833
+ onClick={() => onChange(!checked)}
834
+ className={cn(
835
+ "relative h-5 w-9 cursor-pointer rounded-full border border-border transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
836
+ checked ? "bg-primary/35" : "bg-muted",
837
+ )}
838
+ >
839
+ <span
840
+ className={cn(
841
+ "absolute top-1/2 size-3.5 -translate-y-1/2 rounded-full bg-foreground shadow transition-transform",
842
+ checked ? "translate-x-[18px]" : "translate-x-[3px]",
843
+ )}
844
+ />
845
+ </button>
846
+ );
847
+ }
848
+
849
+ export function VisualSliderControl({
850
+ value,
851
+ onChange,
852
+ min = 0,
853
+ max = 100,
854
+ step = 1,
855
+ unit,
856
+ }: {
857
+ value: number;
858
+ onChange: (value: number) => void;
859
+ min?: number;
860
+ max?: number;
861
+ step?: number;
862
+ unit?: string;
863
+ }) {
864
+ const safeValue = clampNumber(Number.isFinite(value) ? value : min, min, max);
865
+ return (
866
+ <div className="flex h-7 items-center gap-2">
867
+ <input
868
+ type="range"
869
+ min={min}
870
+ max={max}
871
+ step={step}
872
+ value={safeValue}
873
+ onChange={(event) => onChange(Number(event.currentTarget.value))}
874
+ className="min-w-0 flex-1 cursor-pointer accent-foreground"
875
+ />
876
+ <span className="w-9 text-right text-[11px] tabular-nums text-muted-foreground">
877
+ {formatNumber(safeValue, unit)}
878
+ </span>
879
+ </div>
880
+ );
881
+ }
882
+
883
+ export function VisualScrubInput({
884
+ label,
885
+ value,
886
+ onChange,
887
+ min,
888
+ max,
889
+ step = 1,
890
+ unit,
891
+ disabled = false,
892
+ }: {
893
+ label: string;
894
+ value: number;
895
+ onChange: (value: number) => void;
896
+ min?: number;
897
+ max?: number;
898
+ step?: number;
899
+ unit?: string;
900
+ disabled?: boolean;
901
+ }) {
902
+ const id = useId();
903
+ const [draft, setDraft] = useState(() => formatNumber(value, unit));
904
+ const [focused, setFocused] = useState(false);
905
+ const dragRef = useRef<{
906
+ pointerId: number;
907
+ prevX: number;
908
+ dragged: boolean;
909
+ } | null>(null);
910
+
911
+ useEffect(() => {
912
+ if (!focused) setDraft(formatNumber(value, unit));
913
+ }, [focused, unit, value]);
914
+
915
+ const commit = (nextDraft = draft) => {
916
+ const parsed = parseDraftNumber(nextDraft, value);
917
+ const next = clampNumber(parsed, min, max);
918
+ onChange(next);
919
+ setDraft(formatNumber(next, unit));
920
+ };
921
+
922
+ const setNext = (next: number) => {
923
+ const clamped = clampNumber(next, min, max);
924
+ onChange(clamped);
925
+ setDraft(formatNumber(clamped, unit));
926
+ };
927
+
928
+ const onKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
929
+ if (event.key === "Enter") {
930
+ event.preventDefault();
931
+ commit();
932
+ event.currentTarget.blur();
933
+ return;
934
+ }
935
+ if (event.key === "Escape") {
936
+ event.preventDefault();
937
+ setDraft(formatNumber(value, unit));
938
+ event.currentTarget.blur();
939
+ return;
940
+ }
941
+ if (event.key === "ArrowUp" || event.key === "ArrowDown") {
942
+ event.preventDefault();
943
+ const mult =
944
+ event.shiftKey || event.metaKey ? 10 : event.altKey ? 0.1 : 1;
945
+ const direction = event.key === "ArrowUp" ? 1 : -1;
946
+ const base = parseDraftNumber(draft, value);
947
+ setNext(base + direction * step * mult);
948
+ }
949
+ };
950
+
951
+ const onPointerDown = (event: PointerEvent<HTMLLabelElement>) => {
952
+ if (disabled || event.button !== 0) return;
953
+ event.preventDefault();
954
+ dragRef.current = {
955
+ pointerId: event.pointerId,
956
+ prevX: event.clientX,
957
+ dragged: false,
958
+ };
959
+ event.currentTarget.setPointerCapture(event.pointerId);
960
+ };
961
+
962
+ const onPointerMove = (event: PointerEvent<HTMLLabelElement>) => {
963
+ const drag = dragRef.current;
964
+ if (!drag || drag.pointerId !== event.pointerId) return;
965
+ const delta = event.clientX - drag.prevX;
966
+ if (delta === 0) return;
967
+ drag.prevX = event.clientX;
968
+ drag.dragged = true;
969
+ const mult = event.shiftKey || event.metaKey ? 10 : event.altKey ? 0.1 : 1;
970
+ setNext(value + delta * step * mult);
971
+ };
972
+
973
+ const onPointerUp = (event: PointerEvent<HTMLLabelElement>) => {
974
+ const drag = dragRef.current;
975
+ if (!drag || drag.pointerId !== event.pointerId) return;
976
+ event.currentTarget.releasePointerCapture(event.pointerId);
977
+ dragRef.current = null;
978
+ if (!drag.dragged) {
979
+ document.getElementById(id)?.focus();
980
+ }
981
+ };
982
+
983
+ return (
984
+ <div className="flex items-center gap-1.5">
985
+ <label
986
+ htmlFor={id}
987
+ onPointerDown={onPointerDown}
988
+ onPointerMove={onPointerMove}
989
+ onPointerUp={onPointerUp}
990
+ className={cn(
991
+ "flex w-8 shrink-0 cursor-ew-resize select-none items-center justify-center rounded border border-transparent px-1 text-[10px] font-semibold text-muted-foreground hover:border-border hover:bg-accent/60",
992
+ disabled && "cursor-not-allowed opacity-50",
993
+ )}
994
+ >
995
+ {label}
996
+ </label>
997
+ <input
998
+ id={id}
999
+ value={draft}
1000
+ disabled={disabled}
1001
+ onFocus={() => setFocused(true)}
1002
+ onBlur={() => {
1003
+ setFocused(false);
1004
+ commit();
1005
+ }}
1006
+ onChange={(event) => setDraft(event.currentTarget.value)}
1007
+ onKeyDown={onKeyDown}
1008
+ className="h-7 min-w-0 flex-1 rounded-md border border-input bg-background/70 px-2 text-right text-[11px] tabular-nums text-foreground outline-none transition-colors focus:border-ring focus:ring-1 focus:ring-ring disabled:opacity-50"
1009
+ />
1010
+ </div>
1011
+ );
1012
+ }
1013
+
1014
+ export function VisualTweakControl({
1015
+ tweak,
1016
+ value,
1017
+ onChange,
1018
+ className,
1019
+ }: {
1020
+ tweak: VisualTweakDefinition;
1021
+ value: VisualControlValue;
1022
+ onChange: (value: VisualControlValue) => void;
1023
+ className?: string;
1024
+ }) {
1025
+ if (tweak.type === "toggle") {
1026
+ return (
1027
+ <div
1028
+ className={cn("flex h-7 items-center justify-between gap-2", className)}
1029
+ >
1030
+ <span className="truncate text-[11px] text-muted-foreground">
1031
+ {tweak.label}
1032
+ </span>
1033
+ <VisualToggleControl
1034
+ checked={Boolean(value)}
1035
+ onChange={onChange}
1036
+ label={tweak.label}
1037
+ />
1038
+ </div>
1039
+ );
1040
+ }
1041
+
1042
+ const numericValue =
1043
+ typeof value === "number"
1044
+ ? value
1045
+ : typeof value === "string"
1046
+ ? Number(value)
1047
+ : Number(tweak.defaultValue);
1048
+
1049
+ return (
1050
+ <VisualControlRow label={tweak.label} className={className}>
1051
+ {((tweak.type as string) === "color-swatch" ||
1052
+ (tweak.type as string) === "color-swatches") && (
1053
+ <VisualColorPicker
1054
+ label={tweak.label}
1055
+ value={String(value)}
1056
+ documentColors={(tweak.options ?? []).map(
1057
+ (option) => option.color ?? option.value,
1058
+ )}
1059
+ onChange={onChange}
1060
+ />
1061
+ )}
1062
+ {tweak.type === "segment" && (
1063
+ <VisualSegmentedControl
1064
+ options={tweak.options ?? []}
1065
+ value={String(value)}
1066
+ onChange={onChange}
1067
+ />
1068
+ )}
1069
+ {tweak.type === "slider" && (
1070
+ <VisualSliderControl
1071
+ min={tweak.min ?? 0}
1072
+ max={tweak.max ?? 100}
1073
+ step={tweak.step ?? 1}
1074
+ unit={
1075
+ tweak.unit ??
1076
+ (tweak.cssVar?.toLowerCase().includes("radius") ? "px" : undefined)
1077
+ }
1078
+ value={Number.isFinite(numericValue) ? numericValue : 0}
1079
+ onChange={onChange}
1080
+ />
1081
+ )}
1082
+ </VisualControlRow>
1083
+ );
1084
+ }