@mova-ui/react 0.4.4 → 0.4.5

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 (472) hide show
  1. package/dist/index.js +6 -1
  2. package/package.json +1 -1
  3. package/registry/1.json +1 -1
  4. package/registry/10.json +1 -1
  5. package/registry/11.json +1 -1
  6. package/registry/12.json +1 -1
  7. package/registry/13.json +1 -1
  8. package/registry/14.json +1 -1
  9. package/registry/15.json +1 -1
  10. package/registry/16.json +1 -1
  11. package/registry/17.json +1 -1
  12. package/registry/18.json +1 -1
  13. package/registry/19.json +1 -1
  14. package/registry/2.json +1 -1
  15. package/registry/20.json +1 -1
  16. package/registry/21.json +1 -1
  17. package/registry/22.json +1 -1
  18. package/registry/23.json +1 -1
  19. package/registry/24.json +1 -1
  20. package/registry/25.json +1 -1
  21. package/registry/26.json +1 -1
  22. package/registry/27.json +1 -1
  23. package/registry/28.json +1 -1
  24. package/registry/29.json +1 -1
  25. package/registry/3.json +1 -1
  26. package/registry/30.json +1 -1
  27. package/registry/31.json +1 -1
  28. package/registry/32.json +1 -1
  29. package/registry/33.json +1 -1
  30. package/registry/34.json +1 -1
  31. package/registry/35.json +1 -1
  32. package/registry/36.json +1 -1
  33. package/registry/37.json +1 -1
  34. package/registry/4.json +1 -1
  35. package/registry/5.json +1 -1
  36. package/registry/6.json +1 -1
  37. package/registry/7.json +1 -1
  38. package/registry/8.json +1 -1
  39. package/registry/9.json +1 -1
  40. package/registry/ab-segmented-tabs.json +1 -1
  41. package/registry/accordion-blind.json +1 -1
  42. package/registry/accordion-loader.json +1 -1
  43. package/registry/ai-approval-card.json +1 -1
  44. package/registry/ai-code-block.json +1 -1
  45. package/registry/ai-context-meter.json +1 -1
  46. package/registry/ai-fine-tune-card.json +1 -1
  47. package/registry/ai-prompt-bar.json +1 -1
  48. package/registry/ai-sidebar-nav.json +1 -1
  49. package/registry/ai-sources.json +1 -1
  50. package/registry/ai-suggestions.json +1 -1
  51. package/registry/ai-task-list.json +1 -1
  52. package/registry/ai-tool-call.json +1 -1
  53. package/registry/analyzing-image.json +1 -1
  54. package/registry/animated-sidebar.json +1 -1
  55. package/registry/animated-tags.json +1 -1
  56. package/registry/app-icon-load.json +1 -1
  57. package/registry/apple-breathe.json +1 -1
  58. package/registry/apple-equalizer.json +1 -1
  59. package/registry/apple-icon-morph.json +1 -1
  60. package/registry/apple-pulse-dots.json +1 -1
  61. package/registry/apple-scale-pulse.json +1 -1
  62. package/registry/apple-sound-wave.json +1 -1
  63. package/registry/apple-text-reveal.json +1 -1
  64. package/registry/apple-unlock.json +1 -1
  65. package/registry/arc-tracer.json +1 -1
  66. package/registry/bar-cascade.json +1 -1
  67. package/registry/bar-sweep.json +1 -1
  68. package/registry/blur-text.json +1 -1
  69. package/registry/blur-up-char.json +1 -1
  70. package/registry/blur-up-word.json +1 -1
  71. package/registry/bobbing-dots.json +1 -1
  72. package/registry/bookmark-corner-peel.json +1 -1
  73. package/registry/bookmark-save-pill.json +1 -1
  74. package/registry/bookmark-toggle.json +1 -1
  75. package/registry/border-trail-loading.json +1 -1
  76. package/registry/bounce-dots.json +1 -1
  77. package/registry/bounce-in-char.json +1 -1
  78. package/registry/bouncing-bars.json +1 -1
  79. package/registry/bouncing-dots.json +1 -1
  80. package/registry/bouncing-lines.json +1 -1
  81. package/registry/bouncing-square.json +1 -1
  82. package/registry/breathe-ring.json +1 -1
  83. package/registry/breathing-glow.json +1 -1
  84. package/registry/breathing-square.json +1 -1
  85. package/registry/btn-1.json +1 -1
  86. package/registry/btn-10.json +1 -1
  87. package/registry/btn-11.json +1 -1
  88. package/registry/btn-12.json +1 -1
  89. package/registry/btn-13.json +1 -1
  90. package/registry/btn-14.json +1 -1
  91. package/registry/btn-15.json +1 -1
  92. package/registry/btn-16.json +1 -1
  93. package/registry/btn-17.json +1 -1
  94. package/registry/btn-18.json +1 -1
  95. package/registry/btn-19.json +1 -1
  96. package/registry/btn-2.json +1 -1
  97. package/registry/btn-20.json +1 -1
  98. package/registry/btn-21.json +1 -1
  99. package/registry/btn-22.json +1 -1
  100. package/registry/btn-23.json +1 -1
  101. package/registry/btn-24.json +1 -1
  102. package/registry/btn-25.json +1 -1
  103. package/registry/btn-26.json +1 -1
  104. package/registry/btn-27.json +1 -1
  105. package/registry/btn-28.json +1 -1
  106. package/registry/btn-29.json +1 -1
  107. package/registry/btn-3.json +1 -1
  108. package/registry/btn-30.json +1 -1
  109. package/registry/btn-31.json +1 -1
  110. package/registry/btn-32.json +1 -1
  111. package/registry/btn-33.json +1 -1
  112. package/registry/btn-34.json +1 -1
  113. package/registry/btn-35.json +1 -1
  114. package/registry/btn-36.json +1 -1
  115. package/registry/btn-37.json +1 -1
  116. package/registry/btn-4.json +1 -1
  117. package/registry/btn-5.json +1 -1
  118. package/registry/btn-6.json +1 -1
  119. package/registry/btn-7.json +1 -1
  120. package/registry/btn-8.json +1 -1
  121. package/registry/btn-9.json +1 -1
  122. package/registry/button-slide-arrow.json +1 -1
  123. package/registry/cacheable-bandwidth-cost.json +1 -1
  124. package/registry/card-arc-5.json +1 -1
  125. package/registry/card-arc-7.json +1 -1
  126. package/registry/card-carousel-mono.json +1 -1
  127. package/registry/card-carousel.json +1 -1
  128. package/registry/card-cascade-stagger.json +1 -1
  129. package/registry/card-corner-fan.json +1 -1
  130. package/registry/card-cover-flow-mono.json +1 -1
  131. package/registry/card-cover-flow.json +1 -1
  132. package/registry/card-deck-cascade.json +1 -1
  133. package/registry/card-glance-preview.json +1 -1
  134. package/registry/card-hover.json +1 -1
  135. package/registry/card-linear-spread.json +1 -1
  136. package/registry/card-long-arc-5.json +1 -1
  137. package/registry/card-scatter-spread.json +1 -1
  138. package/registry/card-stack-peel.json +1 -1
  139. package/registry/card-stamp-arc.json +1 -1
  140. package/registry/card-time-machine-mono.json +1 -1
  141. package/registry/card-time-machine.json +1 -1
  142. package/registry/card-wheel-fan.json +1 -1
  143. package/registry/category-select.json +1 -1
  144. package/registry/character-stagger.json +1 -1
  145. package/registry/checkbox-draw.json +1 -1
  146. package/registry/checkmark-draw.json +1 -1
  147. package/registry/circle-toggle.json +1 -1
  148. package/registry/circuit-trace-draw.json +1 -1
  149. package/registry/circular-bars.json +1 -1
  150. package/registry/circular-create-button.json +1 -1
  151. package/registry/classic-spinner.json +1 -1
  152. package/registry/classic-toggle.json +1 -1
  153. package/registry/clock-spinner.json +1 -1
  154. package/registry/comet-spinner.json +1 -1
  155. package/registry/compass-needle-deflect.json +1 -1
  156. package/registry/concentric-pulse.json +1 -1
  157. package/registry/concentric-ring.json +1 -1
  158. package/registry/concentric-squares.json +1 -1
  159. package/registry/context-menu.json +1 -1
  160. package/registry/conveyor-loop.json +1 -1
  161. package/registry/credit-score-barcode-meter.json +1 -1
  162. package/registry/cross-spinner.json +1 -1
  163. package/registry/cube-flip-spring.json +1 -1
  164. package/registry/cursor-trail.json +1 -1
  165. package/registry/dash-ring.json +1 -1
  166. package/registry/dashed-spiral.json +1 -1
  167. package/registry/date-position-selector.json +1 -1
  168. package/registry/dia-text-reveal.json +1 -1
  169. package/registry/diamond-grid.json +1 -1
  170. package/registry/diamond-rotate-spring.json +1 -1
  171. package/registry/dislike-toggle.json +1 -1
  172. package/registry/dither-book.json +1 -1
  173. package/registry/domino-cascade.json +1 -1
  174. package/registry/dot-spinner.json +1 -1
  175. package/registry/dots-ring.json +1 -1
  176. package/registry/double-bounce-toggle.json +1 -1
  177. package/registry/double-ring.json +1 -1
  178. package/registry/download-icons.json +1 -1
  179. package/registry/drop-dot.json +1 -1
  180. package/registry/drop-in-char.json +1 -1
  181. package/registry/droplet-squish.json +1 -1
  182. package/registry/dual-arc.json +1 -1
  183. package/registry/dual-magnet-dipole.json +1 -1
  184. package/registry/dynamic-island.json +1 -1
  185. package/registry/elastic-bars.json +1 -1
  186. package/registry/elastic-square.json +1 -1
  187. package/registry/elastic-tag-snap.json +1 -1
  188. package/registry/error-shake.json +1 -1
  189. package/registry/expanding-cross.json +1 -1
  190. package/registry/expanding-icon-tabs.json +1 -1
  191. package/registry/face-id-scan.json +1 -1
  192. package/registry/fade-arc.json +1 -1
  193. package/registry/fade-dots.json +1 -1
  194. package/registry/fade-down.json +1 -1
  195. package/registry/fade-in-char.json +1 -1
  196. package/registry/fade-in-text.json +1 -1
  197. package/registry/fade-in-word.json +1 -1
  198. package/registry/fade-in.json +1 -1
  199. package/registry/fade-up.json +1 -1
  200. package/registry/file-upload-dropzone.json +1 -1
  201. package/registry/filter-tag-pill.json +1 -1
  202. package/registry/flip-square.json +1 -1
  203. package/registry/flip-x-char.json +1 -1
  204. package/registry/flip-y-char.json +1 -1
  205. package/registry/float-char.json +1 -1
  206. package/registry/float-word.json +1 -1
  207. package/registry/floating-diamonds.json +1 -1
  208. package/registry/floating-label-input.json +1 -1
  209. package/registry/fluid-bars.json +1 -1
  210. package/registry/fluid-diamond.json +1 -1
  211. package/registry/fluid-dot-orbit.json +1 -1
  212. package/registry/fluid-skeleton.json +1 -1
  213. package/registry/focus-blur.json +1 -1
  214. package/registry/form-submit-button.json +1 -1
  215. package/registry/french-doors-3d.json +1 -1
  216. package/registry/gear-tooth-step.json +1 -1
  217. package/registry/gears.json +1 -1
  218. package/registry/gelatin-wobble.json +1 -1
  219. package/registry/geometric-iris-shutter.json +1 -1
  220. package/registry/glassmorphic-card.json +1 -1
  221. package/registry/glow-button.json +1 -1
  222. package/registry/glow-text.json +1 -1
  223. package/registry/gooey-popover.json +1 -1
  224. package/registry/gradient-arc.json +1 -1
  225. package/registry/gradient-shimmer.json +1 -1
  226. package/registry/grid-dots.json +1 -1
  227. package/registry/haptic-ring.json +1 -1
  228. package/registry/heartbeat.json +1 -1
  229. package/registry/hex-lattice-draw.json +1 -1
  230. package/registry/hexagon-spinner.json +1 -1
  231. package/registry/hourglass.json +1 -1
  232. package/registry/hover-lift-char.json +1 -1
  233. package/registry/hover-lift-word.json +1 -1
  234. package/registry/hover-link-card.json +1 -1
  235. package/registry/hover-scale-char.json +1 -1
  236. package/registry/hover-scale-word.json +1 -1
  237. package/registry/inertia-skid-stop.json +1 -1
  238. package/registry/infinity-path.json +1 -1
  239. package/registry/input-focus-glow.json +1 -1
  240. package/registry/inset-action-button.json +1 -1
  241. package/registry/inset-button.json +1 -1
  242. package/registry/intersecting-rings.json +1 -1
  243. package/registry/ios-spinner.json +1 -1
  244. package/registry/kinetic-metronome-tick.json +1 -1
  245. package/registry/kinetic-tension-capsule.json +1 -1
  246. package/registry/light-dark-morph.json +1 -1
  247. package/registry/like-toggle.json +1 -1
  248. package/registry/line-spinner.json +1 -1
  249. package/registry/liquid-dots.json +1 -1
  250. package/registry/mac-terminal.json +1 -1
  251. package/registry/magnetic-button.json +1 -1
  252. package/registry/magnetic-disks.json +1 -1
  253. package/registry/magnetic-dots.json +1 -1
  254. package/registry/magnetic-slide-btn.json +1 -1
  255. package/registry/manifest.json +641 -641
  256. package/registry/matrix-grid-loader.json +1 -1
  257. package/registry/minimal-triangle.json +1 -1
  258. package/registry/modular-tile-snap.json +1 -1
  259. package/registry/mono-activity-blue.json +1 -1
  260. package/registry/mono-activity-green.json +1 -1
  261. package/registry/mono-activity-purple.json +1 -1
  262. package/registry/mono-heatmap.json +1 -1
  263. package/registry/mono-rounded-area.json +1 -1
  264. package/registry/mono-rounded-bar.json +1 -1
  265. package/registry/mono-rounded-bubble.json +1 -1
  266. package/registry/mono-rounded-bullet.json +1 -1
  267. package/registry/mono-rounded-candlestick.json +1 -1
  268. package/registry/mono-rounded-composed.json +1 -1
  269. package/registry/mono-rounded-donut.json +1 -1
  270. package/registry/mono-rounded-funnel.json +1 -1
  271. package/registry/mono-rounded-gauge-arc.json +1 -1
  272. package/registry/mono-rounded-heatmap.json +1 -1
  273. package/registry/mono-rounded-kpi.json +1 -1
  274. package/registry/mono-rounded-line.json +1 -1
  275. package/registry/mono-rounded-meter.json +1 -1
  276. package/registry/mono-rounded-polar.json +1 -1
  277. package/registry/mono-rounded-pyramid.json +1 -1
  278. package/registry/mono-rounded-radar.json +1 -1
  279. package/registry/mono-rounded-radial-gauge.json +1 -1
  280. package/registry/mono-rounded-radial-group.json +1 -1
  281. package/registry/mono-rounded-range.json +1 -1
  282. package/registry/mono-rounded-sankey.json +1 -1
  283. package/registry/mono-rounded-scatter.json +1 -1
  284. package/registry/mono-rounded-sparkline.json +1 -1
  285. package/registry/mono-rounded-stacked-bar.json +1 -1
  286. package/registry/mono-rounded-step.json +1 -1
  287. package/registry/mono-rounded-stream.json +1 -1
  288. package/registry/mono-rounded-treemap.json +1 -1
  289. package/registry/mono-rounded-waterfall.json +1 -1
  290. package/registry/mono-stock.json +1 -1
  291. package/registry/morph-action-pill.json +1 -1
  292. package/registry/morph-dot-ring.json +1 -1
  293. package/registry/morph-loader.json +1 -1
  294. package/registry/morph-toggle.json +1 -1
  295. package/registry/morphing-bars.json +1 -1
  296. package/registry/morphing-infinity.json +1 -1
  297. package/registry/morphing-ring.json +1 -1
  298. package/registry/morphing-shape.json +1 -1
  299. package/registry/mouse-follow.json +1 -1
  300. package/registry/multi-select-chips.json +1 -1
  301. package/registry/multi-tab-bar.json +1 -1
  302. package/registry/neon-sign-draw.json +1 -1
  303. package/registry/nested-orbital-gimbal.json +1 -1
  304. package/registry/network-telemetry-matrix.json +1 -1
  305. package/registry/newtons-cradle.json +1 -1
  306. package/registry/noise-decibel-level.json +1 -1
  307. package/registry/obsidian-liquid-wave.json +1 -1
  308. package/registry/offset-rings.json +1 -1
  309. package/registry/orbiting-circles.json +1 -1
  310. package/registry/orbiting-dot.json +1 -1
  311. package/registry/origami-envelope-unfold.json +1 -1
  312. package/registry/origami-shape.json +1 -1
  313. package/registry/otp-input.json +1 -1
  314. package/registry/overview-bar-scrubber-card.json +1 -1
  315. package/registry/page-transition.json +1 -1
  316. package/registry/page-turn-curl.json +1 -1
  317. package/registry/password-toggle.json +1 -1
  318. package/registry/pendulum-bubble-level.json +1 -1
  319. package/registry/pendulum.json +1 -1
  320. package/registry/perspective-flip-stage.json +1 -1
  321. package/registry/perspective-flip.json +1 -1
  322. package/registry/perspective-layout-switcher.json +1 -1
  323. package/registry/physics-dock.json +1 -1
  324. package/registry/pill-tabs.json +1 -1
  325. package/registry/plus-minus-toggle.json +1 -1
  326. package/registry/presets.json +1 -1
  327. package/registry/prism-block-stack.json +1 -1
  328. package/registry/progress-indicator-piano.json +1 -1
  329. package/registry/progress-indicator.json +1 -1
  330. package/registry/progress-stepper.json +1 -1
  331. package/registry/pulsating-dots.json +1 -1
  332. package/registry/pulse-char.json +1 -1
  333. package/registry/pulse-dot.json +1 -1
  334. package/registry/pulse-dots.json +1 -1
  335. package/registry/pulse-square.json +1 -1
  336. package/registry/pulse-word.json +1 -1
  337. package/registry/pulse.json +1 -1
  338. package/registry/pumping-heart.json +1 -1
  339. package/registry/pyramid-block-build.json +1 -1
  340. package/registry/radar-sweep.json +1 -1
  341. package/registry/radial-iris-mask.json +1 -1
  342. package/registry/radial-spinner.json +1 -1
  343. package/registry/radio-scale.json +1 -1
  344. package/registry/range-slider.json +1 -1
  345. package/registry/receipt-tape-print.json +1 -1
  346. package/registry/rectangle-toggle.json +1 -1
  347. package/registry/repost-toggle.json +1 -1
  348. package/registry/reveal-text.json +1 -1
  349. package/registry/ribbon-banner-slide.json +1 -1
  350. package/registry/ring-sweep.json +1 -1
  351. package/registry/ripple-effect.json +1 -1
  352. package/registry/rise-up-word.json +1 -1
  353. package/registry/roller-blind-drop.json +1 -1
  354. package/registry/rolling-tumble.json +1 -1
  355. package/registry/rotate-in-char.json +1 -1
  356. package/registry/rotating-cross.json +1 -1
  357. package/registry/rotating-louvers.json +1 -1
  358. package/registry/rotating-triangle.json +1 -1
  359. package/registry/rudder-issue-board.json +2 -2
  360. package/registry/sales-analytics-dual-bars.json +1 -1
  361. package/registry/sales-overview-radial-dashboard.json +1 -1
  362. package/registry/sales-target-segmented-arc.json +1 -1
  363. package/registry/scale-in-char.json +1 -1
  364. package/registry/scale-in-text.json +1 -1
  365. package/registry/scale-in-word.json +1 -1
  366. package/registry/scale-in.json +1 -1
  367. package/registry/scramble-text.json +1 -1
  368. package/registry/scroll-canvas-unroll.json +1 -1
  369. package/registry/scroll-reveal.json +1 -1
  370. package/registry/search-expand.json +1 -1
  371. package/registry/segmented-arc-meter.json +1 -1
  372. package/registry/segmented-link-stretch.json +1 -1
  373. package/registry/segmented-step-bar.json +1 -1
  374. package/registry/segmented-stepper-dots.json +1 -1
  375. package/registry/select-dropdown.json +1 -1
  376. package/registry/server-performance-step-bars.json +1 -1
  377. package/registry/shape-shift-grid.json +1 -1
  378. package/registry/shimmer-line.json +1 -1
  379. package/registry/shimmer-text.json +1 -1
  380. package/registry/shutter-slide.json +1 -1
  381. package/registry/shutter-step-slices.json +1 -1
  382. package/registry/siri-wave.json +1 -1
  383. package/registry/skeleton-loader.json +1 -1
  384. package/registry/skeleton.json +1 -1
  385. package/registry/skew-x-char.json +1 -1
  386. package/registry/slide-down-char.json +1 -1
  387. package/registry/slide-down-word.json +1 -1
  388. package/registry/slide-left-char.json +1 -1
  389. package/registry/slide-left.json +1 -1
  390. package/registry/slide-right-char.json +1 -1
  391. package/registry/slide-right.json +1 -1
  392. package/registry/slide-up-char.json +1 -1
  393. package/registry/slide-up-text.json +1 -1
  394. package/registry/slide-up-word.json +1 -1
  395. package/registry/sliding-bars.json +1 -1
  396. package/registry/slinky-coil.json +1 -1
  397. package/registry/smart-card-dispenser.json +1 -1
  398. package/registry/smooth-dot-shift.json +1 -1
  399. package/registry/smooth-ring.json +1 -1
  400. package/registry/smooth-rounded-square.json +1 -1
  401. package/registry/solid-switch.json +1 -1
  402. package/registry/spatial-door-portal.json +1 -1
  403. package/registry/spinning-squares.json +1 -1
  404. package/registry/spiral-spinner.json +1 -1
  405. package/registry/split-gate-reveal.json +1 -1
  406. package/registry/spotlight.json +1 -1
  407. package/registry/spring-bars.json +1 -1
  408. package/registry/spring-dot-matrix.json +1 -1
  409. package/registry/spring-hexagon.json +1 -1
  410. package/registry/spring-ring-expand.json +1 -1
  411. package/registry/spring-text-pop.json +1 -1
  412. package/registry/spring-text.json +1 -1
  413. package/registry/square-accordion.json +1 -1
  414. package/registry/square-grid.json +1 -1
  415. package/registry/square-snake.json +1 -1
  416. package/registry/square-spinner.json +1 -1
  417. package/registry/squash-sphere.json +1 -1
  418. package/registry/stacked-bar-pulse.json +1 -1
  419. package/registry/stagger-text.json +1 -1
  420. package/registry/staggered-glass-curtain.json +1 -1
  421. package/registry/sticky-note-peel.json +1 -1
  422. package/registry/sticky-reveal.json +1 -1
  423. package/registry/stretch-x-char.json +1 -1
  424. package/registry/stretch-y-char.json +1 -1
  425. package/registry/stroke-waveform.json +1 -1
  426. package/registry/submenu-flyout.json +1 -1
  427. package/registry/success-check.json +1 -1
  428. package/registry/sudden-brake.json +1 -1
  429. package/registry/swapping-dots.json +1 -1
  430. package/registry/swing-word.json +1 -1
  431. package/registry/swirling-spinner.json +1 -1
  432. package/registry/symmetric-wave.json +1 -1
  433. package/registry/terminal-loader.json +1 -1
  434. package/registry/text-blink.json +1 -1
  435. package/registry/text-dots.json +1 -1
  436. package/registry/text-morph.json +1 -1
  437. package/registry/text-reveal.json +1 -1
  438. package/registry/text-shimmer-wave.json +1 -1
  439. package/registry/text-shimmer.json +1 -1
  440. package/registry/textarea-auto-grow.json +1 -1
  441. package/registry/theme-toggle.json +1 -1
  442. package/registry/tilt-card.json +1 -1
  443. package/registry/timer-preparation-segmented.json +1 -1
  444. package/registry/tracking-in-text.json +1 -1
  445. package/registry/tracking-out-text.json +1 -1
  446. package/registry/trailing-dots.json +1 -1
  447. package/registry/triple-dot-spinner.json +1 -1
  448. package/registry/twin-orbit.json +1 -1
  449. package/registry/typewriter-text.json +1 -1
  450. package/registry/typing-indicator.json +1 -1
  451. package/registry/typing.json +1 -1
  452. package/registry/use-mouse-position.json +1 -1
  453. package/registry/use-reduced-motion.json +1 -1
  454. package/registry/use-scroll-progress.json +1 -1
  455. package/registry/use-stagger.json +1 -1
  456. package/registry/use-web-haptics.json +1 -1
  457. package/registry/users-growth-pill-progress.json +1 -1
  458. package/registry/utils.json +1 -1
  459. package/registry/vertical-wheel-counter.json +1 -1
  460. package/registry/views-hourly-wave-chart.json +1 -1
  461. package/registry/wandering-cube.json +1 -1
  462. package/registry/wandering-eyes.json +1 -1
  463. package/registry/watch-spinner.json +1 -1
  464. package/registry/wave-dots.json +1 -1
  465. package/registry/wave-physics-loader.json +1 -1
  466. package/registry/wave-reveal.json +1 -1
  467. package/registry/waveform-loader.json +1 -1
  468. package/registry/word-reveal.json +1 -1
  469. package/registry/zig-zag-pulse.json +1 -1
  470. package/registry/zoom-in-text.json +1 -1
  471. package/registry/zoom-in.json +1 -1
  472. package/registry/zoom-out-text.json +1 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "rudder-issue-board",
3
3
  "tier": "free",
4
- "version": "0.4.4",
4
+ "version": "0.4.5",
5
5
  "file": "rudder-issue-board.tsx",
6
6
  "sourcePath": "ui/ai/rudder-issue-board.tsx",
7
7
  "registryFile": "rudder-issue-board.json",
@@ -20,5 +20,5 @@
20
20
  ],
21
21
  "license": "Apache-2.0",
22
22
  "previewMode": "client",
23
- "content": "\"use client\";\n\"use client\";\n\n// SPDX-License-Identifier: Apache-2.0\n// Adapted from Rudder's KanbanBoard, StatusIcon, and PriorityIcon.\n// The bundled third-party notices retain Rudder's LICENSE and NOTICE.\nimport {\n DndContext,\n DragOverlay,\n KeyboardSensor,\n PointerSensor,\n closestCenter,\n pointerWithin,\n useDroppable,\n useSensor,\n useSensors,\n type CollisionDetection,\n type DragCancelEvent,\n type DragEndEvent,\n type DragOverEvent,\n type DragStartEvent,\n type KeyboardCoordinateGetter,\n} from \"@dnd-kit/core\";\nimport { SortableContext, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy } from \"@dnd-kit/sortable\";\nimport { CSS } from \"@dnd-kit/utilities\";\nimport { CalendarClock, FolderKanban, Plus, Tags, UserCheck, Filter, List, Columns3, SlidersHorizontal, ArrowUpDown, ChevronDown, Check } from \"lucide-react\";\nimport { AnimatePresence, motion, useReducedMotion } from \"framer-motion\";\nimport { Fragment, createContext, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, useId, type ReactNode, type RefObject } from \"react\";\n\nexport type BoardStatus = \"backlog\" | \"todo\" | \"in_progress\" | \"in_review\" | \"blocked\" | \"done\" | \"cancelled\";\n\nexport interface BoardIssue {\n assignee: string;\n created: string;\n updated?: string;\n id: string;\n labels: string[];\n priority: \"critical\" | \"high\" | \"medium\" | \"low\";\n project: string;\n reviewer?: string;\n status: BoardStatus;\n title: string;\n}\n\nexport interface RudderIssueBoardProps {\n theme?: \"dark\" | \"light\";\n className?: string;\n initialIssues?: readonly BoardIssue[];\n onIssuesChange?: (issues: BoardIssue[]) => void;\n}\n\nconst BOARD_STATUSES: readonly BoardStatus[] = [\"backlog\", \"todo\", \"in_progress\", \"in_review\", \"blocked\", \"done\", \"cancelled\"];\nconst AI_THEME_STYLES = \"/* Shared public tokens for the standalone AI component family. */\\n.mova-ai-theme {\\n color-scheme: light;\\n color: var(--mova-ai-ink, #202020);\\n font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \\\"Segoe UI\\\", sans-serif;\\n font-size: 13px;\\n line-height: 1.45;\\n letter-spacing: -0.01em;\\n\\n --mova-ai-page: #f7f7f7;\\n --mova-ai-canvas: #f4f4f4;\\n --mova-ai-surface: #ffffff;\\n --mova-ai-inset: #f8f8f8;\\n --mova-ai-hover: #f1f1f1;\\n --mova-ai-hover-strong: #e9e9e9;\\n\\n --mova-ai-ink: #202020;\\n --mova-ai-ink-secondary: #666666;\\n --mova-ai-ink-muted: #858585;\\n\\n --mova-ai-border: #dedede;\\n --mova-ai-border-soft: #ededed;\\n --mova-ai-border-strong: #c8c8c8;\\n --mova-ai-field: #f6f6f6;\\n --mova-ai-grid: color-mix(in srgb, var(--mova-ai-border) 78%, transparent);\\n --mova-ai-stripe: rgb(32 35 38 / 7%);\\n --mova-ai-stripe-bg: #242424;\\n\\n --mova-ai-accent: #454545;\\n --mova-ai-accent-ink: #333333;\\n --mova-ai-accent-foreground: #ffffff;\\n --mova-ai-accent-tint: #eeeeee;\\n --mova-ai-success: #28734d;\\n --mova-ai-success-tint: #e8f3ec;\\n --mova-ai-warning: #986517;\\n --mova-ai-warning-tint: #f8f0df;\\n --mova-ai-danger: #a33c3c;\\n --mova-ai-danger-tint: #f7eaea;\\n --mova-ai-info: #496b89;\\n --mova-ai-info-tint: #eaf0f5;\\n --mova-ai-neutral-status: #69737d;\\n --mova-ai-status-on: #ffffff;\\n\\n --mova-ai-tooltip-bg: #292929;\\n --mova-ai-tooltip-fg: #f6f6f6;\\n --mova-ai-tooltip-muted: #c3c3c3;\\n --mova-ai-tooltip-border: #484848;\\n\\n --mova-ai-radius-panel: 8px;\\n --mova-ai-radius-control: 8px;\\n --mova-ai-shadow-xs: 0 1px 2px rgb(22 28 33 / 5%);\\n --mova-ai-shadow-sm: 0 2px 8px rgb(22 28 33 / 7%);\\n --mova-ai-shadow-md: 0 8px 20px rgb(22 28 33 / 11%);\\n --mova-ai-shadow-lg: 0 14px 32px rgb(22 28 33 / 15%);\\n --mova-ai-shadow-hairline: 0 0 0 1px var(--mova-ai-border);\\n --mova-ai-shadow-btn: 0 0 0 1px var(--mova-ai-border-strong), var(--mova-ai-shadow-xs);\\n --mova-ai-shadow-card: 0 0 0 1px var(--mova-ai-border), var(--mova-ai-shadow-sm);\\n --mova-ai-shadow-raised: 0 0 0 1px var(--mova-ai-border), var(--mova-ai-shadow-md);\\n --mova-ai-shadow-overlay: 0 0 0 1px var(--mova-ai-border), var(--mova-ai-shadow-lg);\\n --mova-ai-shadow-inset-field: inset 0 1px 2px rgb(22 28 33 / 12%);\\n}\\n\\n.mova-ai-theme[data-theme=\\\"dark\\\"] {\\n color-scheme: dark;\\n --mova-ai-page: #181818;\\n --mova-ai-canvas: #202020;\\n --mova-ai-surface: #272727;\\n --mova-ai-inset: #1d1d1d;\\n --mova-ai-hover: #2d2d2d;\\n --mova-ai-hover-strong: #393939;\\n\\n --mova-ai-ink: #eeeeee;\\n --mova-ai-ink-secondary: #b5b5b5;\\n --mova-ai-ink-muted: #929292;\\n\\n --mova-ai-border: #3b3b3b;\\n --mova-ai-border-soft: #303030;\\n --mova-ai-border-strong: #505050;\\n --mova-ai-field: #2c2c2c;\\n --mova-ai-grid: color-mix(in srgb, var(--mova-ai-border) 78%, transparent);\\n --mova-ai-stripe: rgb(255 255 255 / 6%);\\n --mova-ai-stripe-bg: #202528;\\n\\n --mova-ai-accent: #c2c2c2;\\n --mova-ai-accent-ink: #d5d5d5;\\n --mova-ai-accent-foreground: #202020;\\n --mova-ai-accent-tint: #353535;\\n --mova-ai-success: #88c7a0;\\n --mova-ai-success-tint: #293b30;\\n --mova-ai-warning: #d7b66f;\\n --mova-ai-warning-tint: #3c3525;\\n --mova-ai-danger: #e5928e;\\n --mova-ai-danger-tint: #3c2b2b;\\n --mova-ai-info: #9bbbd6;\\n --mova-ai-info-tint: #293541;\\n --mova-ai-neutral-status: #a0a0a0;\\n --mova-ai-status-on: #181818;\\n\\n --mova-ai-tooltip-bg: #121212;\\n --mova-ai-tooltip-fg: #eeeeee;\\n --mova-ai-tooltip-muted: #b5b5b5;\\n --mova-ai-tooltip-border: #3b3b3b;\\n\\n --mova-ai-shadow-xs: 0 1px 2px rgb(0 0 0 / 22%);\\n --mova-ai-shadow-sm: 0 2px 8px rgb(0 0 0 / 25%);\\n --mova-ai-shadow-md: 0 8px 20px rgb(0 0 0 / 30%);\\n --mova-ai-shadow-lg: 0 14px 32px rgb(0 0 0 / 38%);\\n --mova-ai-shadow-hairline: 0 0 0 1px var(--mova-ai-border);\\n --mova-ai-shadow-btn: 0 0 0 1px var(--mova-ai-border-strong), var(--mova-ai-shadow-xs);\\n --mova-ai-shadow-card: 0 0 0 1px var(--mova-ai-border), var(--mova-ai-shadow-sm);\\n --mova-ai-shadow-raised: 0 0 0 1px var(--mova-ai-border), var(--mova-ai-shadow-md);\\n --mova-ai-shadow-overlay: 0 0 0 1px var(--mova-ai-border), var(--mova-ai-shadow-lg);\\n --mova-ai-shadow-inset-field: inset 0 1px 2px rgb(0 0 0 / 42%);\\n}\\n\";\n\nconst createBoardKeyboardCoordinates = (\n getHorizontalTarget: () => BoardStatus | null,\n onHorizontalTarget: (status: BoardStatus | null) => void,\n): KeyboardCoordinateGetter => (event, args) => {\n if (event.code !== \"ArrowLeft\" && event.code !== \"ArrowRight\") {\n onHorizontalTarget(null);\n return sortableKeyboardCoordinates(event, args);\n }\n\n event.preventDefault();\n const { context } = args;\n const currentStatus = String(getHorizontalTarget() ?? context.active?.data.current?.status ?? \"\") as BoardStatus;\n const lanes: Array<{ rect: NonNullable<ReturnType<typeof context.droppableRects.get>>; status: BoardStatus }> = [];\n\n context.droppableContainers.getEnabled().forEach((container) => {\n const data = container.data.current;\n if (data?.type !== \"column\" && data?.type !== \"hidden-column\") return;\n const rect = context.droppableRects.get(container.id);\n if (rect && BOARD_STATUSES.includes(data.status as BoardStatus)) {\n lanes.push({ rect, status: data.status as BoardStatus });\n }\n });\n\n lanes.sort((a, b) => a.rect.left - b.rect.left || a.rect.top - b.rect.top);\n const currentIndex = lanes.findIndex((lane) => lane.status === currentStatus);\n const targetIndex = currentIndex + (event.code === \"ArrowRight\" ? 1 : -1);\n const targetLane = lanes[targetIndex];\n if (!targetLane) {\n onHorizontalTarget(null);\n return;\n }\n onHorizontalTarget(targetLane.status);\n\n const currentCenterY = context.collisionRect\n ? context.collisionRect.top + context.collisionRect.height / 2\n : targetLane.rect.top;\n let targetRect = targetLane.rect;\n let nearestDistance = Number.POSITIVE_INFINITY;\n\n context.droppableContainers.getEnabled().forEach((container) => {\n const data = container.data.current;\n if (data?.type !== \"issue\" || data.status !== targetLane.status) return;\n const rect = context.droppableRects.get(container.id);\n if (!rect) return;\n const distance = Math.abs(rect.top + rect.height / 2 - currentCenterY);\n if (distance < nearestDistance) {\n nearestDistance = distance;\n targetRect = rect;\n }\n });\n\n const activeRect = context.collisionRect;\n return {\n x: targetRect.left + (targetRect.width - (activeRect?.width ?? 0)) / 2,\n y: targetRect.top + (targetRect.height - (activeRect?.height ?? 0)) / 2,\n };\n};\n\nconst STATUS_META: Record<BoardStatus, { color: string; label: string; lane: string; laneOver: string }> = {\n backlog: { color: \"var(--mova-ai-neutral-status)\", label: \"Backlog\", lane: \"border-[var(--mova-ai-border,#3b3b3b)] bg-[var(--mova-ai-canvas,#202020)]\", laneOver: \"border-[var(--mova-ai-border-strong,#505050)] bg-[var(--mova-ai-hover,#2d2d2d)]\" },\n todo: { color: \"var(--mova-ai-neutral-status)\", label: \"To do\", lane: \"border-[var(--mova-ai-border,#3b3b3b)] bg-[var(--mova-ai-canvas,#202020)]\", laneOver: \"border-[var(--mova-ai-border-strong,#505050)] bg-[var(--mova-ai-hover,#2d2d2d)]\" },\n in_progress: { color: \"var(--mova-ai-info)\", label: \"In progress\", lane: \"border-[var(--mova-ai-border,#3b3b3b)] bg-[var(--mova-ai-canvas,#202020)]\", laneOver: \"border-[var(--mova-ai-border-strong,#505050)] bg-[var(--mova-ai-hover,#2d2d2d)]\" },\n in_review: { color: \"var(--mova-ai-warning)\", label: \"In review\", lane: \"border-[var(--mova-ai-border,#3b3b3b)] bg-[var(--mova-ai-canvas,#202020)]\", laneOver: \"border-[var(--mova-ai-border-strong,#505050)] bg-[var(--mova-ai-hover,#2d2d2d)]\" },\n blocked: { color: \"var(--mova-ai-danger)\", label: \"Blocked\", lane: \"border-[var(--mova-ai-border,#3b3b3b)] bg-[var(--mova-ai-canvas,#202020)]\", laneOver: \"border-[var(--mova-ai-border-strong,#505050)] bg-[var(--mova-ai-hover,#2d2d2d)]\" },\n done: { color: \"var(--mova-ai-success)\", label: \"Done\", lane: \"border-[var(--mova-ai-border,#3b3b3b)] bg-[var(--mova-ai-canvas,#202020)]\", laneOver: \"border-[var(--mova-ai-border-strong,#505050)] bg-[var(--mova-ai-hover,#2d2d2d)]\" },\n cancelled: { color: \"var(--mova-ai-neutral-status)\", label: \"Cancelled\", lane: \"border-[var(--mova-ai-border,#3b3b3b)] bg-[var(--mova-ai-canvas,#202020)]\", laneOver: \"border-[var(--mova-ai-border-strong,#505050)] bg-[var(--mova-ai-hover,#2d2d2d)]\" },\n};\n\nconst DEFAULT_ISSUES: readonly BoardIssue[] = [\n { id: \"R6Z-141\", title: \"Retire legacy Node backend, CLI, and MCP authorities\", status: \"backlog\", priority: \"high\", assignee: \"Noah\", project: \"Rudder-dev\", labels: [\"Feature\"], created: \"Aug 20\" },\n { id: \"R6Z-140\", title: \"Cut over the public listener and Rust authorities\", status: \"backlog\", priority: \"high\", assignee: \"Avery\", project: \"Rudder-dev\", labels: [\"Feature\"], created: \"Aug 20\" },\n { id: \"R6Z-170\", title: \"Proposal: add history versions and Diff view\", status: \"todo\", priority: \"medium\", assignee: \"Luca\", project: \"Rudder-dev\", labels: [\"Feature\"], created: \"Aug 27\" },\n { id: \"R6Z-166\", title: \"Define the issue event contract for agent handoffs\", status: \"todo\", priority: \"medium\", assignee: \"Mira\", project: \"Rudder-dev\", labels: [\"Spec\"], created: \"Aug 26\" },\n { id: \"R6Z-181\", title: \"Share 500-index issue context through MCP and CLI\", status: \"in_progress\", priority: \"high\", assignee: \"Sana\", project: \"Rudder-dev\", labels: [\"Feature\"], created: \"Aug 30\" },\n { id: \"R6Z-210\", title: \"Re-run the Rust output spool after a capacity failure\", status: \"in_progress\", priority: \"high\", assignee: \"Noah\", reviewer: \"Mira\", project: \"Rudder-dev\", labels: [\"Bug\"], created: \"Sep 14\" },\n { id: \"R6Z-207\", title: \"Remove the loaded Issues hint from the shell\", status: \"done\", priority: \"medium\", assignee: \"Theo\", project: \"Rudder-dev\", labels: [\"Feature\"], created: \"Aug 19\" },\n];\n\nfunction StatusIcon({ status }: { status: BoardStatus }) {\n const common = { stroke: \"currentColor\", strokeWidth: 2, strokeLinecap: \"round\" as const };\n return (\n <span className=\"inline-flex h-4 w-4 shrink-0 items-center justify-center\" data-slot=\"issue-status-icon\" data-status={status} style={{ color: STATUS_META[status].color }} aria-hidden=\"true\">\n <svg viewBox=\"0 0 16 16\" className=\"h-full w-full overflow-visible\" fill=\"none\">\n {status === \"backlog\" ? <circle cx=\"8\" cy=\"8\" r=\"5.5\" {...common} strokeDasharray=\"1 3\" /> : null}\n {status === \"todo\" ? <circle cx=\"8\" cy=\"8\" r=\"5.5\" {...common} /> : null}\n {status === \"in_progress\" ? <><circle cx=\"8\" cy=\"8\" r=\"5.5\" {...common} opacity=\"0.22\" /><path d=\"M8 2.5a5.5 5.5 0 0 1 5.5 5.5\" {...common} strokeWidth=\"2.4\" /><path d=\"M13.5 8a5.5 5.5 0 0 1-5.5 5.5\" {...common} strokeWidth=\"2.4\" opacity=\"0.72\" /></> : null}\n {status === \"in_review\" ? <><circle cx=\"8\" cy=\"8\" r=\"5.5\" {...common} /><circle cx=\"8\" cy=\"8\" r=\"2\" fill=\"currentColor\" /></> : null}\n {status === \"done\" ? <><circle cx=\"8\" cy=\"8\" r=\"6.5\" fill=\"currentColor\" /><path d=\"M4.75 8.25 7 10.5l4.25-5\" stroke=\"var(--mova-ai-status-on,#fff)\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" /></> : null}\n {status === \"cancelled\" ? <><circle cx=\"8\" cy=\"8\" r=\"6\" fill=\"currentColor\" opacity=\"0.16\" /><circle cx=\"8\" cy=\"8\" r=\"5.5\" {...common} /><path d=\"m5.25 5.25 5.5 5.5m0-5.5-5.5 5.5\" {...common} /></> : null}\n {status === \"blocked\" ? <><path d=\"M5.1 2.25h5.8l3.05 3.05v5.4l-3.05 3.05H5.1L2.05 10.7V5.3Z\" {...common} strokeLinejoin=\"round\" /><path d=\"m5.25 5.25 5.5 5.5\" {...common} /></> : null}\n </svg>\n </span>\n );\n}\n\nfunction PriorityIcon({ priority }: { priority: BoardIssue[\"priority\"] }) {\n const active = priority === \"critical\" ? 4 : priority === \"high\" ? 3 : priority === \"medium\" ? 2 : 1;\n const tone = priority === \"critical\" ? \"var(--mova-ai-danger)\" : priority === \"high\" ? \"var(--mova-ai-warning)\" : priority === \"medium\" ? \"var(--mova-ai-info)\" : \"var(--mova-ai-neutral-status)\";\n const heights = [4, 6, 10, 14];\n return (\n <span className=\"inline-flex h-3.5 w-4 items-end gap-[2px]\" style={{ color: tone }} aria-hidden=\"true\">\n {heights.map((height, index) => <span className=\"w-[3px] rounded-[1px] bg-current\" key={height} style={{ height: `${height}px`, opacity: index < active ? 1 : 0.25 }} />)}\n </span>\n );\n}\n\nconst DISPLAY_FIELDS = [\"Identifier\", \"Priority\", \"Assignee\", \"Reviewer\", \"Labels\", \"Project\", \"Updated\", \"Created\"] as const;\ntype DisplayField = typeof DISPLAY_FIELDS[number];\nconst DisplayContext = createContext<readonly DisplayField[]>(DISPLAY_FIELDS.filter((field) => field !== \"Updated\"));\n\nfunction MemberAvatar({ name }: { name: string }) {\n return <span aria-hidden=\"true\" className=\"inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-[var(--mova-ai-hover-strong,#e9e9e9)] text-[9px] font-semibold text-[color:var(--mova-ai-ink,#202020)]\">{name.slice(0, 1)}</span>;\n}\n\nfunction IssueListRow({ issue }: { issue: BoardIssue }) {\n const display = useContext(DisplayContext);\n return <article className=\"rudder-list-row border-b border-[var(--mova-ai-border-soft,#ededed)] px-4 py-3\" data-testid={`issue-row-${issue.id}`}>\n <div className=\"flex min-w-0 items-start gap-2.5\">\n <span className=\"mt-0.5\" title={STATUS_META[issue.status].label}><span className=\"sr-only\">{STATUS_META[issue.status].label}</span><StatusIcon status={issue.status} /></span>\n {display.includes(\"Identifier\") ? <span className=\"shrink-0 pt-0.5 font-mono text-xs text-[color:var(--mova-ai-ink-muted,#858585)]\">{issue.id}</span> : null}\n <h3 className=\"min-w-0 flex-1 break-words text-sm font-medium leading-relaxed text-[color:var(--mova-ai-ink,#202020)]\">{issue.title}</h3>\n </div>\n <div className=\"rudder-list-meta flex min-w-0 flex-wrap items-center gap-x-3 gap-y-2 text-xs text-[color:var(--mova-ai-ink-secondary,#666)]\">\n {display.includes(\"Priority\") ? <span title={`${issue.priority} priority`}><span className=\"sr-only\">{issue.priority} priority</span><PriorityIcon priority={issue.priority} /></span> : null}\n {display.includes(\"Labels\") ? issue.labels.map((label) => <span key={label} className=\"inline-flex max-w-full items-center gap-1.5 rounded-full border border-[var(--mova-ai-border,#dedede)] px-2 py-0.5\"><span className=\"h-1.5 w-1.5 shrink-0 rounded-full bg-[var(--mova-ai-neutral-status,#69737d)]\" /><span className=\"break-words\">{label}</span></span>) : null}\n {display.includes(\"Assignee\") ? <span className=\"inline-flex min-w-0 items-center gap-1.5\"><MemberAvatar name={issue.assignee} /><span className=\"break-words\">{issue.assignee}</span></span> : null}\n {display.includes(\"Reviewer\") && issue.reviewer ? <span className=\"inline-flex items-center gap-1\" title=\"Reviewer\"><UserCheck className=\"h-3 w-3 shrink-0\" />{issue.reviewer}</span> : null}\n {display.includes(\"Project\") ? <span className=\"rudder-list-project break-words\">{issue.project}</span> : null}\n {display.includes(\"Created\") ? <span className=\"whitespace-nowrap\">{issue.created}</span> : null}\n {display.includes(\"Updated\") ? <span className=\"whitespace-nowrap\">Updated {issue.updated ?? issue.created}</span> : null}\n </div>\n </article>;\n}\n\nfunction IssueCardBody({ issue }: { issue: BoardIssue }) {\n const display = useContext(DisplayContext);\n return (\n <div className=\"min-w-0\">\n <div className=\"flex min-w-0 items-start gap-2\">\n <span className=\"min-w-0 flex-1 font-mono text-xs text-[color:var(--mova-ai-ink-muted,#858585)]\">{display.includes(\"Identifier\") ? issue.id : \"\"}</span>\n {display.includes(\"Assignee\") ? <span className=\"inline-flex min-w-0 max-w-[8.5rem] shrink items-center gap-1 overflow-hidden text-xs text-[color:var(--mova-ai-ink-secondary,#666)]\">\n <MemberAvatar name={issue.assignee} />\n <span className=\"truncate\">{issue.assignee}</span>\n </span> : null}\n </div>\n <p className=\"mt-2 break-words text-sm leading-snug text-[color:var(--mova-ai-ink,#202020)]\">{issue.title}</p>\n {display.includes(\"Priority\") ? <div className=\"mt-2 flex min-w-0 items-center gap-1.5 overflow-hidden text-xs text-[color:var(--mova-ai-ink-muted,#858585)]\">\n <PriorityIcon priority={issue.priority} />\n <span className=\"capitalize\">{issue.priority}</span>\n </div> : null}\n <div className=\"mt-2 space-y-1.5 border-t border-[var(--mova-ai-border-soft,#ededed)] pt-2 text-xs text-[color:var(--mova-ai-ink-muted,#858585)]\">\n {display.includes(\"Project\") ? <div className=\"grid min-w-0 grid-cols-[4.5rem_minmax(0,1fr)] items-center gap-2\"><span className=\"inline-flex items-center gap-1.5\"><FolderKanban className=\"h-3 w-3\" />Project</span><span className=\"truncate text-right\">{issue.project}</span></div> : null}\n {display.includes(\"Reviewer\") && issue.reviewer ? <div className=\"grid min-w-0 grid-cols-[4.5rem_minmax(0,1fr)] items-center gap-2\"><span className=\"inline-flex items-center gap-1.5\"><UserCheck className=\"h-3 w-3\" />Reviewer</span><span className=\"truncate text-right\">{issue.reviewer}</span></div> : null}\n {display.includes(\"Labels\") ? <div className=\"grid min-w-0 grid-cols-[4.5rem_minmax(0,1fr)] items-center gap-2\"><span className=\"inline-flex items-center gap-1.5\"><Tags className=\"h-3 w-3\" />Labels</span><span className=\"truncate text-right\"><span className=\"inline-flex items-center gap-1 rounded-full border border-[var(--mova-ai-border,#dedede)] px-1.5 py-0.5 text-[10px]\"><span className=\"h-1.5 w-1.5 rounded-full bg-[var(--mova-ai-neutral-status,#69737d)]\" />{issue.labels[0] ?? \"None\"}</span></span></div> : null}\n {display.includes(\"Created\") ? <div className=\"grid min-w-0 grid-cols-[4.5rem_minmax(0,1fr)] items-center gap-2\"><span className=\"inline-flex items-center gap-1.5\"><CalendarClock className=\"h-3 w-3\" />Created</span><span className=\"truncate text-right\">{issue.created}</span></div> : null}\n {display.includes(\"Updated\") ? <div className=\"grid grid-cols-[4.5rem_minmax(0,1fr)] gap-2\"><span>Updated</span><span className=\"truncate text-right\">{issue.updated ?? issue.created}</span></div> : null}\n </div>\n </div>\n );\n}\n\nfunction SortableIssueCard({ issue }: { issue: BoardIssue }) {\n const { attributes, isDragging, listeners, setNodeRef, transform, transition } = useSortable({ id: issue.id, data: { status: issue.status, type: \"issue\" } });\n return (\n <article\n ref={setNodeRef}\n style={{ transform: CSS.Transform.toString(transform), transition }}\n {...attributes}\n {...listeners}\n className={`motion-kanban-card cursor-grab touch-pan-x overflow-hidden rounded-[9px] border border-[var(--mova-ai-border,#dedede)] bg-[var(--mova-ai-surface,#fff)] p-2.5 outline-none transition-[box-shadow,opacity,border-color] motion-reduce:transition-none focus-visible:border-[var(--mova-ai-border-strong,#c8c8c8)] focus-visible:ring-2 focus-visible:ring-[var(--mova-ai-border-strong,#c8c8c8)] active:cursor-grabbing ${isDragging ? \"opacity-25\" : \"hover:border-[var(--mova-ai-border-strong,#c8c8c8)] hover:shadow-sm\"}`}\n data-dragging={isDragging ? \"true\" : \"false\"}\n data-issue-id={issue.id}\n data-testid={`kanban-card-${issue.id}`}\n >\n <IssueCardBody issue={issue} />\n </article>\n );\n}\n\nfunction BoardColumn({ issues, status, onCreate }: { issues: BoardIssue[]; status: BoardStatus; onCreate: (status: BoardStatus, trigger: HTMLElement) => void }) {\n const { isOver, setNodeRef } = useDroppable({ id: status, data: { status, type: \"column\" } });\n const meta = STATUS_META[status];\n return (\n <div className=\"flex min-h-0 w-[260px] min-w-[260px] shrink-0 flex-col\">\n <div className=\"mb-1 flex items-center gap-2 px-2 py-2\">\n <StatusIcon status={status} />\n <span className=\"text-xs font-semibold uppercase tracking-wide text-[color:var(--mova-ai-ink-secondary,#666)]\">{meta.label}</span>\n <span className=\"ml-auto text-xs tabular-nums text-[color:var(--mova-ai-ink-muted,#858585)]\">{issues.length}</span>\n <button type=\"button\" onClick={(event) => onCreate(status, event.currentTarget)} aria-label={`Create issue in ${meta.label}`} className=\"rounded p-1 hover:bg-[var(--mova-ai-hover-strong,#e9e9e9)] focus-visible:ring-2 focus-visible:ring-[var(--mova-ai-border-strong,#c8c8c8)]\"><Plus className=\"h-3.5 w-3.5 text-[color:var(--mova-ai-ink-muted,#858585)]\" aria-hidden=\"true\" /></button>\n </div>\n <div\n ref={setNodeRef}\n className={`motion-kanban-lane min-h-[160px] flex-1 space-y-1.5 overflow-y-auto rounded-[9px] border p-1.5 transition-colors motion-reduce:transition-none ${isOver ? meta.laneOver : meta.lane}`}\n data-over={isOver ? \"true\" : \"false\"}\n data-testid={`kanban-column-${status}`}\n >\n <SortableContext items={issues.map((issue) => issue.id)} strategy={verticalListSortingStrategy}>\n {issues.map((issue) => <Fragment key={issue.id}><SortableIssueCard issue={issue} /></Fragment>)}\n </SortableContext>\n </div>\n </div>\n );\n}\n\nfunction HiddenStatus({ status, onCreate }: { status: BoardStatus; onCreate: (status: BoardStatus, trigger: HTMLElement) => void }) {\n const { isOver, setNodeRef } = useDroppable({ id: status, data: { status, type: \"hidden-column\" } });\n const meta = STATUS_META[status];\n return (\n <div ref={setNodeRef} className={`motion-kanban-lane flex items-center gap-2 rounded-[9px] border px-2 py-2 transition-colors motion-reduce:transition-none ${isOver ? meta.laneOver : meta.lane}`} data-over={isOver ? \"true\" : \"false\"} data-testid={`kanban-hidden-column-${status}`}>\n <StatusIcon status={status} />\n <span className=\"min-w-0 flex-1 text-xs font-semibold uppercase tracking-wide text-[color:var(--mova-ai-ink-secondary,#666)]\">{meta.label}</span>\n <span className=\"text-xs tabular-nums text-[color:var(--mova-ai-ink-muted,#858585)]\">0</span>\n <button type=\"button\" onClick={(event) => onCreate(status, event.currentTarget)} aria-label={`Create issue in ${meta.label}`} className=\"rounded p-1 hover:bg-[var(--mova-ai-hover-strong,#e9e9e9)] focus-visible:ring-2 focus-visible:ring-[var(--mova-ai-border-strong,#c8c8c8)]\"><Plus className=\"h-3.5 w-3.5 text-[color:var(--mova-ai-ink-muted,#858585)]\" aria-hidden=\"true\" /></button>\n </div>\n );\n}\n\nfunction ToolbarPopover({ label, icon, children, triggerRef }: { label: string; icon: ReactNode; children: ReactNode; triggerRef?: RefObject<HTMLButtonElement | null> }) {\n const reducedMotion = useReducedMotion();\n const [open, setOpen] = useState(false);\n const root = useRef<HTMLDivElement>(null);\n const trigger = useRef<HTMLButtonElement>(null);\n const panel = useRef<HTMLDivElement>(null);\n const id = useId();\n useLayoutEffect(() => {\n if (!open) return;\n const container = root.current;\n const popup = panel.current;\n const board = container?.closest<HTMLElement>('section[aria-label=\"Rudder issue board\"]');\n if (!container || !popup || !board) return;\n const position = () => {\n const anchor = container.getBoundingClientRect();\n const bounds = board.getBoundingClientRect();\n // Convert viewport measurements back to local CSS pixels for scaled previews.\n const scale = container.offsetWidth ? anchor.width / container.offsetWidth : 1;\n if (!scale) return;\n const inset = 8 * scale;\n popup.style.maxWidth = `${Math.max(0, (bounds.width - inset * 2) / scale)}px`;\n const width = popup.getBoundingClientRect().width;\n const left = Math.max(bounds.left + inset, Math.min(anchor.right - width, bounds.right - inset - width));\n popup.style.left = `${(left - anchor.left) / scale}px`;\n popup.style.right = \"auto\";\n };\n position();\n const observer = new ResizeObserver(position);\n observer.observe(board);\n observer.observe(container);\n window.addEventListener(\"resize\", position);\n return () => { observer.disconnect(); window.removeEventListener(\"resize\", position); };\n }, [open]);\n useEffect(() => {\n if (!open) return;\n panel.current?.querySelector<HTMLElement>(\"input:checked, button, input\")?.focus();\n const dismiss = (event: PointerEvent) => {\n if (!root.current?.contains(event.target as Node)) { setOpen(false); trigger.current?.focus(); }\n };\n document.addEventListener(\"pointerdown\", dismiss);\n return () => document.removeEventListener(\"pointerdown\", dismiss);\n }, [open]);\n return <div ref={root} className=\"relative\" onBlur={(event) => { if (event.relatedTarget && !event.currentTarget.contains(event.relatedTarget as Node)) setOpen(false); }} onKeyDown={(event) => { if (event.key === \"Escape\" && open) { event.preventDefault(); event.stopPropagation(); setOpen(false); trigger.current?.focus(); } }}>\n <button ref={(node) => { trigger.current = node; if (triggerRef) triggerRef.current = node; }} type=\"button\" aria-label={label === \"Sort\" ? \"Sort issues\" : label === \"Filter\" ? \"Filter issues by status\" : \"Display fields\"} aria-expanded={open} aria-controls={id} onClick={() => setOpen((value) => !value)} className={`flex items-center gap-1.5 rounded-md px-2 py-1.5 transition-colors hover:bg-[var(--mova-ai-hover,#f1f1f1)] ${open ? \"bg-[var(--mova-ai-hover-strong,#e9e9e9)] text-[color:var(--mova-ai-ink,#202020)]\" : \"\"}`}>{icon}{label}<ChevronDown className=\"h-3 w-3\" /></button>\n <AnimatePresence initial={false}>{open ? <motion.div key=\"panel\" ref={panel} id={id} role=\"group\" aria-label={`${label} options`} initial={{ opacity: 0, y: reducedMotion ? 0 : -4 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: reducedMotion ? 0 : -3 }} transition={{ duration: reducedMotion ? 0 : 0.16, ease: [0.22, 1, 0.36, 1] }} className=\"absolute right-0 z-30 mt-2 w-48 space-y-1 rounded-lg border border-[var(--mova-ai-border,#dedede)] bg-[var(--mova-ai-surface,#fff)] p-2 text-xs shadow-xl\">{children}</motion.div> : null}</AnimatePresence>\n </div>;\n}\n\nfunction resolveTargetStatus(overId: string, issues: BoardIssue[]): BoardStatus | null {\n if (BOARD_STATUSES.includes(overId as BoardStatus)) return overId as BoardStatus;\n return issues.find((issue) => issue.id === overId)?.status ?? null;\n}\n\nexport function RudderIssueBoard({ theme = \"dark\", className = \"\", initialIssues = DEFAULT_ISSUES, onIssuesChange }: RudderIssueBoardProps) {\n const rootRef = useRef<HTMLElement>(null);\n const sortGroupId = useId();\n const keyboardTargetRef = useRef<BoardStatus | null>(null);\n const [issues, setIssues] = useState<BoardIssue[]>(() => [...initialIssues]);\n const [statusFilter, setStatusFilter] = useState(\"all\");\n const [sort, setSort] = useState(\"Manual\");\n const [descending, setDescending] = useState(false);\n const [view, setView] = useState<\"board\" | \"list\">(\"board\");\n const [display, setDisplay] = useState<DisplayField[]>(DISPLAY_FIELDS.filter((field) => field !== \"Updated\"));\n const [creating, setCreating] = useState<BoardStatus | null>(null);\n const [draft, setDraft] = useState(\"\");\n const createInputRef = useRef<HTMLInputElement>(null);\n const createTriggerRef = useRef<HTMLElement | null>(null);\n const listCreateRef = useRef<HTMLButtonElement>(null);\n const statusFilterRef = useRef<HTMLButtonElement>(null);\n const createOpen = creating !== null;\n const restoreCreateFocus = () => requestAnimationFrame(() => {\n const original = createTriggerRef.current;\n (original?.isConnected ? original : listCreateRef.current ?? statusFilterRef.current)?.focus();\n });\n const openCreate = (status: BoardStatus, trigger: HTMLElement) => { createTriggerRef.current = trigger; setCreating(status); setDraft(\"\"); };\n const closeCreate = () => { setCreating(null); restoreCreateFocus(); };\n useEffect(() => { if (createOpen) createInputRef.current?.focus(); }, [createOpen]);\n const orderedIssues = useMemo(() => {\n const matches = issues;\n if (sort === \"Manual\") return matches;\n const dateValue = (value: string): string | number => { const timestamp = Date.parse(value); return Number.isNaN(timestamp) ? value : timestamp; };\n return [...matches].sort((a, b) => {\n const value = (issue: BoardIssue): string | number => sort === \"Status\" ? BOARD_STATUSES.indexOf(issue.status) : sort === \"Priority\" ? [\"critical\", \"high\", \"medium\", \"low\"].indexOf(issue.priority) : sort === \"Title\" ? issue.title : dateValue(sort === \"Updated\" ? issue.updated ?? issue.created : issue.created);\n const av = value(a), bv = value(b);\n const delta = typeof av === \"number\" && typeof bv === \"number\" ? av - bv : String(av).localeCompare(String(bv), undefined, { numeric: true });\n return descending ? -delta : delta;\n });\n }, [issues, sort, descending]);\n const filtered = useMemo(() => orderedIssues.filter((issue) => statusFilter === \"all\" || issue.status === statusFilter), [orderedIssues, statusFilter]);\n const [activeId, setActiveId] = useState<string | null>(null);\n const [overStatus, setOverStatus] = useState<BoardStatus | null>(null);\n const [announcement, setAnnouncement] = useState(\"Issue board ready. Pick up a card to move it between status columns.\");\n const [reduceMotion, setReduceMotion] = useState(false);\n const keyboardCoordinates = useMemo(\n () => createBoardKeyboardCoordinates(\n () => keyboardTargetRef.current,\n (status) => { keyboardTargetRef.current = status; },\n ),\n [],\n );\n const sensors = useSensors(\n useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),\n useSensor(KeyboardSensor, { coordinateGetter: keyboardCoordinates }),\n );\n const grouped = useMemo(() => Object.fromEntries(BOARD_STATUSES.map((status) => [status, filtered.filter((issue) => issue.status === status)])) as Record<BoardStatus, BoardIssue[]>, [filtered]);\n const visibleStatuses = BOARD_STATUSES.filter((status) => grouped[status].length > 0);\n const hiddenStatuses = BOARD_STATUSES.filter((status) => grouped[status].length === 0);\n const activeIssue = activeId ? issues.find((issue) => issue.id === activeId) ?? null : null;\n\n useEffect(() => {\n const media = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n const update = () => setReduceMotion(media.matches);\n update();\n media.addEventListener(\"change\", update);\n return () => media.removeEventListener(\"change\", update);\n }, []);\n\n const collisionDetection: CollisionDetection = (args) => {\n if (!args.pointerCoordinates && keyboardTargetRef.current) {\n const target = args.droppableContainers.find((container) => container.id === keyboardTargetRef.current);\n if (target) return [{ id: target.id, data: { droppableContainer: target, value: 0 } }];\n }\n const pointerCollisions = pointerWithin(args);\n if (pointerCollisions.length > 0) {\n const issueCollision = pointerCollisions.find(({ id }) =>\n args.droppableContainers.find((container) => container.id === id)?.data.current?.type === \"issue\"\n );\n if (issueCollision) return [issueCollision];\n\n const laneCollision = pointerCollisions.find(({ id }) => {\n const type = args.droppableContainers.find((container) => container.id === id)?.data.current?.type;\n return type === \"column\" || type === \"hidden-column\";\n });\n const lane = laneCollision\n ? args.droppableContainers.find((container) => container.id === laneCollision.id)\n : null;\n const targetStatus = lane?.data.current?.status as BoardStatus | undefined;\n const laneIssues = targetStatus\n ? args.droppableContainers.filter((container) =>\n container.id !== args.active.id\n && container.data.current?.type === \"issue\"\n && container.data.current?.status === targetStatus\n )\n : [];\n const laneIssueBottom = Math.max(\n ...laneIssues.map((container) => args.droppableRects.get(container.id)?.bottom ?? Number.NEGATIVE_INFINITY),\n );\n if (args.pointerCoordinates && args.pointerCoordinates.y <= laneIssueBottom) {\n const nearestIssue = closestCenter({ ...args, droppableContainers: laneIssues })[0];\n if (nearestIssue) return [nearestIssue];\n }\n\n return pointerCollisions;\n }\n return closestCenter(args);\n };\n\n const clearDrag = (event?: DragCancelEvent) => {\n keyboardTargetRef.current = null;\n setActiveId(null);\n setOverStatus(null);\n if (event) setAnnouncement(\"Move cancelled.\");\n };\n\n const handleDragStart = ({ active }: DragStartEvent) => {\n const issue = issues.find((candidate) => candidate.id === String(active.id));\n setActiveId(String(active.id));\n if (issue) setAnnouncement(`Picked up ${issue.id}. Current status: ${STATUS_META[issue.status].label}.`);\n };\n\n const handleDragOver = ({ over }: DragOverEvent) => {\n setOverStatus(over ? resolveTargetStatus(String(over.id), issues) : null);\n };\n\n const handleDragEnd = ({ active, over }: DragEndEvent) => {\n clearDrag();\n if (!over) {\n setAnnouncement(\"Move cancelled.\");\n return;\n }\n const activeIssueId = String(active.id);\n const overId = String(over.id);\n const targetStatus = resolveTargetStatus(overId, issues);\n const movingIssue = issues.find((issue) => issue.id === activeIssueId);\n if (!targetStatus || !movingIssue) return;\n\n const sourceStatus = movingIssue.status;\n const allGrouped = Object.fromEntries(BOARD_STATUSES.map((status) => [status, orderedIssues.filter((issue) => issue.status === status)])) as Record<BoardStatus, BoardIssue[]>;\n const targetLaneBeforeMove = allGrouped[targetStatus];\n const nextByStatus = Object.fromEntries(BOARD_STATUSES.map((status) => [status, allGrouped[status].filter((issue) => issue.id !== activeIssueId)])) as Record<BoardStatus, BoardIssue[]>;\n const targetLane = nextByStatus[targetStatus];\n const overIndex = targetLaneBeforeMove.findIndex((issue) => issue.id === overId);\n const insertAt = overIndex >= 0 ? Math.min(overIndex, targetLane.length) : targetLane.length;\n targetLane.splice(insertAt, 0, { ...movingIssue, status: targetStatus });\n const nextIssues = BOARD_STATUSES.flatMap((status) => nextByStatus[status]);\n\n if (sourceStatus !== targetStatus || nextIssues.map((issue) => issue.id).join(\"|\") !== issues.map((issue) => issue.id).join(\"|\")) {\n setSort(\"Manual\");\n setIssues(nextIssues);\n onIssuesChange?.(nextIssues);\n setAnnouncement(`${movingIssue.id} moved to ${STATUS_META[targetStatus].label}.`);\n requestAnimationFrame(() => requestAnimationFrame(() => {\n const movedCard = Array.from(rootRef.current?.querySelectorAll<HTMLElement>(\"[data-issue-id]\") ?? [])\n .find((card) => card.dataset.issueId === activeIssueId);\n (movedCard ?? statusFilterRef.current)?.focus({ preventScroll: true });\n }));\n } else {\n setAnnouncement(`${movingIssue.id} stayed in ${STATUS_META[targetStatus].label}.`);\n }\n };\n\n return (\n <section ref={rootRef} data-theme={theme} className={`mova-ai-theme rudder-board w-full min-w-0 min-h-[620px] bg-[var(--mova-ai-canvas,#202020)] text-[color:var(--mova-ai-ink,#eeeeee)] ${className}`} aria-label=\"Rudder issue board\">\n <style>{AI_THEME_STYLES}{`\n .rudder-board :is(button,input,select,summary):focus-visible { outline: 2px solid var(--mova-ai-accent,#c2c2c2); outline-offset: 2px; }\n .rudder-board { container-type: inline-size; }\n .rudder-list-row { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 40%); align-items: start; gap: 16px; }\n .rudder-list-meta { justify-content: flex-end; padding-top: 2px; }\n @container (max-width: 640px) {\n .rudder-list-row { grid-template-columns: minmax(0, 1fr); gap: 8px; }\n .rudder-list-meta { justify-content: flex-start; padding-left: 26px; }\n }\n @media (prefers-reduced-motion: reduce) {\n .motion-kanban-card,\n .motion-kanban-lane {\n animation: none !important;\n transition: none !important;\n }\n }\n `}</style>\n <p className=\"sr-only\" aria-live=\"assertive\">{announcement}</p>\n <div className=\"flex flex-wrap items-center gap-2 border-b border-[var(--mova-ai-border,#dedede)] px-4 py-3 text-xs text-[color:var(--mova-ai-ink-secondary,#666)]\">\n <h2 className=\"mr-auto text-xs font-medium uppercase tracking-[0.16em] text-[color:var(--mova-ai-ink-secondary,#666)]\">Issues</h2>\n <div className=\"flex flex-wrap items-center justify-end gap-1\">\n <button type=\"button\" aria-label=\"Show working issues\" aria-pressed={statusFilter === \"in_progress\"} onClick={() => setStatusFilter((value) => value === \"in_progress\" ? \"all\" : \"in_progress\")} className={`mr-1 rounded-md border border-[var(--mova-ai-border,#dedede)] px-2.5 py-1.5 tabular-nums transition-colors hover:bg-[var(--mova-ai-hover,#f1f1f1)] ${statusFilter === \"in_progress\" ? \"bg-[var(--mova-ai-hover-strong,#e9e9e9)] text-[color:var(--mova-ai-ink,#202020)]\" : \"\"}`}>{issues.filter((issue) => issue.status === \"in_progress\").length} working</button>\n <div className=\"mr-1 flex rounded-md border border-[var(--mova-ai-border,#dedede)] p-0.5\"><button type=\"button\" aria-label=\"List view\" aria-pressed={view === \"list\"} onClick={() => setView(\"list\")} className={`rounded p-1 transition-colors ${view === \"list\" ? \"bg-[var(--mova-ai-hover-strong,#e9e9e9)] text-[color:var(--mova-ai-ink,#202020)]\" : \"\"}`}><List className=\"h-3.5 w-3.5\" /></button><button type=\"button\" aria-label=\"Board view\" aria-pressed={view === \"board\"} onClick={() => setView(\"board\")} className={`rounded p-1 transition-colors ${view === \"board\" ? \"bg-[var(--mova-ai-hover-strong,#e9e9e9)] text-[color:var(--mova-ai-ink,#202020)]\" : \"\"}`}><Columns3 className=\"h-3.5 w-3.5\" /></button></div>\n <ToolbarPopover label=\"Filter\" triggerRef={statusFilterRef} icon={<Filter className=\"h-3 w-3\" />}>\n <div role=\"radiogroup\" aria-label=\"Issue status\">{[\"all\", ...BOARD_STATUSES].map((status) => <label key={status} className=\"flex cursor-pointer items-center gap-2 rounded px-2 py-1.5 hover:bg-[var(--mova-ai-hover,#f1f1f1)]\"><input type=\"radio\" className=\"accent-[var(--mova-ai-accent,#454545)]\" name={`${sortGroupId}-status`} checked={statusFilter === status} onChange={() => setStatusFilter(status)} />{status === \"all\" ? \"All statuses\" : STATUS_META[status as BoardStatus].label}</label>)}</div>\n </ToolbarPopover>\n <ToolbarPopover label=\"Sort\" icon={<ArrowUpDown className=\"h-3 w-3\" />}>\n <div role=\"radiogroup\" aria-label=\"Sort field\">{[\"Manual\", \"Status\", \"Priority\", \"Title\", \"Created\", \"Updated\"].map((value) => <label key={value} className={`flex cursor-pointer items-center gap-2 rounded px-2 py-1.5 hover:bg-[var(--mova-ai-hover,#f1f1f1)] ${sort === value ? \"bg-[var(--mova-ai-hover,#f1f1f1)] text-[color:var(--mova-ai-ink,#202020)]\" : \"\"}`}><input className=\"accent-[var(--mova-ai-accent,#454545)]\" type=\"radio\" name={sortGroupId} checked={sort === value} onChange={() => setSort(value)} />{value}{sort === value ? <Check className=\"ml-auto h-3 w-3\" /> : null}</label>)}</div>\n <button type=\"button\" disabled={sort === \"Manual\"} onClick={() => setDescending((value) => !value)} aria-label=\"Toggle sort direction\" className=\"flex w-full items-center gap-2 border-t border-[var(--mova-ai-border,#dedede)] px-2 py-2 text-left disabled:opacity-40\"><ArrowUpDown className=\"h-3 w-3\" />{descending ? \"Descending\" : \"Ascending\"}</button>\n </ToolbarPopover>\n <ToolbarPopover label=\"Display\" icon={<SlidersHorizontal className=\"h-3 w-3\" />}>{DISPLAY_FIELDS.map((field) => <label key={field} className=\"flex cursor-pointer items-center gap-2 rounded px-2 py-1.5 hover:bg-[var(--mova-ai-hover,#f1f1f1)]\"><input className=\"accent-[var(--mova-ai-accent,#454545)]\" type=\"checkbox\" checked={display.includes(field)} onChange={() => setDisplay((fields) => fields.includes(field) ? fields.filter((value) => value !== field) : [...fields, field])} />{field}</label>)}</ToolbarPopover>\n </div>\n </div>\n {creating ? <form onKeyDown={(event) => { if (event.key === \"Escape\") { event.preventDefault(); closeCreate(); } }} className=\"flex flex-wrap items-center gap-2 border-b border-[var(--mova-ai-border,#dedede)] bg-[var(--mova-ai-hover,#f1f1f1)] p-3\" onSubmit={(event) => { event.preventDefault(); if (!draft.trim()) return; let sequence = issues.length + 1; while (issues.some((issue) => issue.id === `ISS-${sequence}`)) sequence++; const next = [...issues, { id: `ISS-${sequence}`, title: draft.trim(), status: creating, priority: \"medium\" as const, assignee: \"Unassigned\", project: \"No project\", labels: [], created: new Date().toISOString().slice(0, 10) }]; setIssues(next); onIssuesChange?.(next); setStatusFilter(\"all\"); closeCreate(); setAnnouncement(\"Issue created.\"); }}><input ref={createInputRef} required aria-label=\"New issue title\" placeholder=\"Issue title\" value={draft} onChange={(event) => setDraft(event.target.value)} className=\"min-w-0 flex-1 rounded border border-[var(--mova-ai-border,#dedede)] bg-[var(--mova-ai-field,#f6f6f6)] px-2 py-1.5 text-sm\" /><select aria-label=\"New issue status\" value={creating} onChange={(event) => setCreating(event.target.value as BoardStatus)} className=\"rounded bg-[var(--mova-ai-canvas,#f4f4f4)] p-1 text-xs\">{BOARD_STATUSES.map((status) => <option key={status} value={status}>{STATUS_META[status].label}</option>)}</select><button type=\"submit\" className=\"rounded bg-[var(--mova-ai-accent,#454545)] px-3 py-1.5 text-xs text-[color:var(--mova-ai-accent-foreground,#fff)]\">Create</button><button type=\"button\" onClick={closeCreate} className=\"px-2 text-xs\">Cancel</button></form> : null}\n <DisplayContext.Provider value={display}>\n <AnimatePresence mode=\"wait\" initial={false}>\n <motion.div key={view} initial={{ opacity: 0, y: reduceMotion ? 0 : 5 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: reduceMotion ? 0 : -3 }} transition={{ duration: reduceMotion ? 0 : 0.15, ease: [0.22, 1, 0.36, 1] }}>\n {view === \"list\" ? <div className=\"max-h-[580px] overflow-y-auto\">{filtered.map((issue) => <IssueListRow key={issue.id} issue={issue} />)}{filtered.length === 0 ? <p className=\"py-10 text-center text-sm text-[color:var(--mova-ai-ink-secondary,#666)]\">No issues match this filter.</p> : null}<button ref={listCreateRef} type=\"button\" onClick={(event) => openCreate(statusFilter === \"all\" ? \"backlog\" : statusFilter as BoardStatus, event.currentTarget)} className=\"flex items-center gap-2 px-4 py-3 text-xs text-[color:var(--mova-ai-ink-secondary,#666)] transition-colors hover:text-[color:var(--mova-ai-ink,#202020)]\"><Plus className=\"h-3.5 w-3.5\" />Add issue</button></div> :\n <DndContext\n autoScroll={{ acceleration: 2, interval: 10, threshold: { x: 0.08, y: 0.2 } }}\n collisionDetection={collisionDetection}\n onDragCancel={clearDrag}\n onDragEnd={handleDragEnd}\n onDragOver={handleDragOver}\n onDragStart={handleDragStart}\n sensors={sensors}\n >\n <div className=\"overflow-x-auto overscroll-x-contain p-3 sm:p-4\">\n <div className=\"flex min-h-[520px] min-w-max items-stretch gap-3\">\n {visibleStatuses.map((status) => <Fragment key={status}><BoardColumn issues={grouped[status]} status={status} onCreate={openCreate} /></Fragment>)}\n {hiddenStatuses.length > 0 ? (\n <div className={`motion-kanban-lane flex min-h-0 w-[228px] min-w-[228px] shrink-0 flex-col rounded-[10px] border p-2 transition-colors motion-reduce:transition-none ${overStatus && hiddenStatuses.includes(overStatus) ? \"border-[var(--mova-ai-border-strong,#c8c8c8)] bg-[var(--mova-ai-hover,#f1f1f1)]\" : \"border-[var(--mova-ai-border,#dedede)] bg-[var(--mova-ai-canvas,#f4f4f4)]\"}`} data-testid=\"kanban-hidden-columns\">\n <div className=\"mb-2 flex items-center gap-2 px-1\">\n <span className=\"text-xs font-semibold uppercase tracking-wide text-[color:var(--mova-ai-ink-muted,#858585)]\">Hidden columns</span>\n <span className=\"ml-auto text-xs tabular-nums text-[color:var(--mova-ai-ink-muted,#858585)]\">{hiddenStatuses.length}</span>\n </div>\n <div className=\"space-y-2\">{hiddenStatuses.map((status) => <Fragment key={status}><HiddenStatus status={status} onCreate={openCreate} /></Fragment>)}</div>\n </div>\n ) : null}\n </div>\n </div>\n <DragOverlay dropAnimation={reduceMotion ? null : { duration: 180, easing: \"cubic-bezier(0.22, 1, 0.36, 1)\" }}>\n {activeIssue ? <article className=\"w-[247px] rotate-[0.4deg] scale-[1.015] overflow-hidden rounded-[9px] border border-[var(--mova-ai-border-strong,#c8c8c8)] bg-[var(--mova-ai-surface,#fff)] p-2.5 shadow-[0_18px_48px_rgba(0,0,0,.48)]\"><IssueCardBody issue={activeIssue} /></article> : null}\n </DragOverlay>\n </DndContext>}\n </motion.div>\n </AnimatePresence>\n </DisplayContext.Provider>\n </section>\n );\n}\n\nexport default RudderIssueBoard;\n"
23
+ "content": "\"use client\";\n\"use client\";\n\n// SPDX-License-Identifier: Apache-2.0\n// Adapted from Rudder's KanbanBoard, StatusIcon, and PriorityIcon.\n// The bundled third-party notices retain Rudder's LICENSE and NOTICE.\nimport {\n DndContext,\n DragOverlay,\n KeyboardSensor,\n PointerSensor,\n closestCenter,\n pointerWithin,\n useDroppable,\n useSensor,\n useSensors,\n type CollisionDetection,\n type DragCancelEvent,\n type DragEndEvent,\n type DragOverEvent,\n type DragStartEvent,\n type KeyboardCoordinateGetter,\n} from \"@dnd-kit/core\";\nimport { SortableContext, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy } from \"@dnd-kit/sortable\";\nimport { CSS } from \"@dnd-kit/utilities\";\nimport { CalendarClock, FolderKanban, Plus, Tags, UserCheck, Filter, List, Columns3, SlidersHorizontal, ArrowUpDown, ChevronDown, Check } from \"lucide-react\";\nimport { AnimatePresence, motion, useReducedMotion } from \"framer-motion\";\nimport { Fragment, createContext, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, useId, type ReactNode, type RefObject, type CSSProperties } from \"react\";\n\nexport type BoardStatus = \"backlog\" | \"todo\" | \"in_progress\" | \"in_review\" | \"blocked\" | \"done\" | \"cancelled\";\n\nexport interface BoardIssue {\n assignee: string;\n created: string;\n updated?: string;\n id: string;\n labels: string[];\n priority: \"critical\" | \"high\" | \"medium\" | \"low\";\n project: string;\n reviewer?: string;\n status: BoardStatus;\n title: string;\n}\n\nexport interface RudderIssueBoardProps {\n theme?: \"dark\" | \"light\";\n className?: string;\n initialIssues?: readonly BoardIssue[];\n onIssuesChange?: (issues: BoardIssue[]) => void;\n}\n\nconst BOARD_STATUSES: readonly BoardStatus[] = [\"backlog\", \"todo\", \"in_progress\", \"in_review\", \"blocked\", \"done\", \"cancelled\"];\nconst AI_THEME_STYLES = \"/* Shared public tokens for the standalone AI component family. */\\n.mova-ai-theme {\\n color-scheme: light;\\n color: var(--mova-ai-ink, #202020);\\n font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \\\"Segoe UI\\\", sans-serif;\\n font-size: 13px;\\n line-height: 1.45;\\n letter-spacing: -0.01em;\\n\\n --mova-ai-page: #f7f7f7;\\n --mova-ai-canvas: #f4f4f4;\\n --mova-ai-surface: #ffffff;\\n --mova-ai-inset: #f8f8f8;\\n --mova-ai-hover: #f1f1f1;\\n --mova-ai-hover-strong: #e9e9e9;\\n\\n --mova-ai-ink: #202020;\\n --mova-ai-ink-secondary: #666666;\\n --mova-ai-ink-muted: #858585;\\n\\n --mova-ai-border: #dedede;\\n --mova-ai-border-soft: #ededed;\\n --mova-ai-border-strong: #c8c8c8;\\n --mova-ai-field: #f6f6f6;\\n --mova-ai-grid: color-mix(in srgb, var(--mova-ai-border) 78%, transparent);\\n --mova-ai-stripe: rgb(32 35 38 / 7%);\\n --mova-ai-stripe-bg: #242424;\\n\\n --mova-ai-accent: #454545;\\n --mova-ai-accent-ink: #333333;\\n --mova-ai-accent-foreground: #ffffff;\\n --mova-ai-accent-tint: #eeeeee;\\n --mova-ai-success: #28734d;\\n --mova-ai-success-tint: #e8f3ec;\\n --mova-ai-warning: #986517;\\n --mova-ai-warning-tint: #f8f0df;\\n --mova-ai-danger: #a33c3c;\\n --mova-ai-danger-tint: #f7eaea;\\n --mova-ai-info: #496b89;\\n --mova-ai-info-tint: #eaf0f5;\\n --mova-ai-neutral-status: #69737d;\\n --mova-ai-status-on: #ffffff;\\n\\n --mova-ai-tooltip-bg: #292929;\\n --mova-ai-tooltip-fg: #f6f6f6;\\n --mova-ai-tooltip-muted: #c3c3c3;\\n --mova-ai-tooltip-border: #484848;\\n\\n --mova-ai-radius-panel: 8px;\\n --mova-ai-radius-control: 8px;\\n --mova-ai-shadow-xs: 0 1px 2px rgb(22 28 33 / 5%);\\n --mova-ai-shadow-sm: 0 2px 8px rgb(22 28 33 / 7%);\\n --mova-ai-shadow-md: 0 8px 20px rgb(22 28 33 / 11%);\\n --mova-ai-shadow-lg: 0 14px 32px rgb(22 28 33 / 15%);\\n --mova-ai-shadow-hairline: 0 0 0 1px var(--mova-ai-border);\\n --mova-ai-shadow-btn: 0 0 0 1px var(--mova-ai-border-strong), var(--mova-ai-shadow-xs);\\n --mova-ai-shadow-card: 0 0 0 1px var(--mova-ai-border), var(--mova-ai-shadow-sm);\\n --mova-ai-shadow-raised: 0 0 0 1px var(--mova-ai-border), var(--mova-ai-shadow-md);\\n --mova-ai-shadow-overlay: 0 0 0 1px var(--mova-ai-border), var(--mova-ai-shadow-lg);\\n --mova-ai-shadow-inset-field: inset 0 1px 2px rgb(22 28 33 / 12%);\\n}\\n\\n.mova-ai-theme[data-theme=\\\"dark\\\"] {\\n color-scheme: dark;\\n --mova-ai-page: #181818;\\n --mova-ai-canvas: #202020;\\n --mova-ai-surface: #272727;\\n --mova-ai-inset: #1d1d1d;\\n --mova-ai-hover: #2d2d2d;\\n --mova-ai-hover-strong: #393939;\\n\\n --mova-ai-ink: #eeeeee;\\n --mova-ai-ink-secondary: #b5b5b5;\\n --mova-ai-ink-muted: #929292;\\n\\n --mova-ai-border: #3b3b3b;\\n --mova-ai-border-soft: #303030;\\n --mova-ai-border-strong: #505050;\\n --mova-ai-field: #2c2c2c;\\n --mova-ai-grid: color-mix(in srgb, var(--mova-ai-border) 78%, transparent);\\n --mova-ai-stripe: rgb(255 255 255 / 6%);\\n --mova-ai-stripe-bg: #202528;\\n\\n --mova-ai-accent: #c2c2c2;\\n --mova-ai-accent-ink: #d5d5d5;\\n --mova-ai-accent-foreground: #202020;\\n --mova-ai-accent-tint: #353535;\\n --mova-ai-success: #88c7a0;\\n --mova-ai-success-tint: #293b30;\\n --mova-ai-warning: #d7b66f;\\n --mova-ai-warning-tint: #3c3525;\\n --mova-ai-danger: #e5928e;\\n --mova-ai-danger-tint: #3c2b2b;\\n --mova-ai-info: #9bbbd6;\\n --mova-ai-info-tint: #293541;\\n --mova-ai-neutral-status: #a0a0a0;\\n --mova-ai-status-on: #181818;\\n\\n --mova-ai-tooltip-bg: #121212;\\n --mova-ai-tooltip-fg: #eeeeee;\\n --mova-ai-tooltip-muted: #b5b5b5;\\n --mova-ai-tooltip-border: #3b3b3b;\\n\\n --mova-ai-shadow-xs: 0 1px 2px rgb(0 0 0 / 22%);\\n --mova-ai-shadow-sm: 0 2px 8px rgb(0 0 0 / 25%);\\n --mova-ai-shadow-md: 0 8px 20px rgb(0 0 0 / 30%);\\n --mova-ai-shadow-lg: 0 14px 32px rgb(0 0 0 / 38%);\\n --mova-ai-shadow-hairline: 0 0 0 1px var(--mova-ai-border);\\n --mova-ai-shadow-btn: 0 0 0 1px var(--mova-ai-border-strong), var(--mova-ai-shadow-xs);\\n --mova-ai-shadow-card: 0 0 0 1px var(--mova-ai-border), var(--mova-ai-shadow-sm);\\n --mova-ai-shadow-raised: 0 0 0 1px var(--mova-ai-border), var(--mova-ai-shadow-md);\\n --mova-ai-shadow-overlay: 0 0 0 1px var(--mova-ai-border), var(--mova-ai-shadow-lg);\\n --mova-ai-shadow-inset-field: inset 0 1px 2px rgb(0 0 0 / 42%);\\n}\\n\";\n\nconst createBoardKeyboardCoordinates = (\n getHorizontalTarget: () => BoardStatus | null,\n onHorizontalTarget: (status: BoardStatus | null) => void,\n): KeyboardCoordinateGetter => (event, args) => {\n if (event.code !== \"ArrowLeft\" && event.code !== \"ArrowRight\") {\n onHorizontalTarget(null);\n return sortableKeyboardCoordinates(event, args);\n }\n\n event.preventDefault();\n const { context } = args;\n const currentStatus = String(getHorizontalTarget() ?? context.active?.data.current?.status ?? \"\") as BoardStatus;\n const lanes: Array<{ rect: NonNullable<ReturnType<typeof context.droppableRects.get>>; status: BoardStatus }> = [];\n\n context.droppableContainers.getEnabled().forEach((container) => {\n const data = container.data.current;\n if (data?.type !== \"column\" && data?.type !== \"hidden-column\") return;\n const rect = context.droppableRects.get(container.id);\n if (rect && BOARD_STATUSES.includes(data.status as BoardStatus)) {\n lanes.push({ rect, status: data.status as BoardStatus });\n }\n });\n\n lanes.sort((a, b) => a.rect.left - b.rect.left || a.rect.top - b.rect.top);\n const currentIndex = lanes.findIndex((lane) => lane.status === currentStatus);\n const targetIndex = currentIndex + (event.code === \"ArrowRight\" ? 1 : -1);\n const targetLane = lanes[targetIndex];\n if (!targetLane) {\n onHorizontalTarget(null);\n return;\n }\n onHorizontalTarget(targetLane.status);\n\n const currentCenterY = context.collisionRect\n ? context.collisionRect.top + context.collisionRect.height / 2\n : targetLane.rect.top;\n let targetRect = targetLane.rect;\n let nearestDistance = Number.POSITIVE_INFINITY;\n\n context.droppableContainers.getEnabled().forEach((container) => {\n const data = container.data.current;\n if (data?.type !== \"issue\" || data.status !== targetLane.status) return;\n const rect = context.droppableRects.get(container.id);\n if (!rect) return;\n const distance = Math.abs(rect.top + rect.height / 2 - currentCenterY);\n if (distance < nearestDistance) {\n nearestDistance = distance;\n targetRect = rect;\n }\n });\n\n const activeRect = context.collisionRect;\n return {\n x: targetRect.left + (targetRect.width - (activeRect?.width ?? 0)) / 2,\n y: targetRect.top + (targetRect.height - (activeRect?.height ?? 0)) / 2,\n };\n};\n\nconst STATUS_META: Record<BoardStatus, { color: string; label: string; lane: string; laneOver: string }> = {\n backlog: { color: \"var(--mova-ai-neutral-status)\", label: \"Backlog\", lane: \"border-[var(--mova-ai-border,#3b3b3b)] bg-[var(--mova-ai-canvas,#202020)]\", laneOver: \"border-[var(--mova-ai-border-strong,#505050)] bg-[var(--mova-ai-hover,#2d2d2d)]\" },\n todo: { color: \"var(--mova-ai-neutral-status)\", label: \"To do\", lane: \"border-[var(--mova-ai-border,#3b3b3b)] bg-[var(--mova-ai-canvas,#202020)]\", laneOver: \"border-[var(--mova-ai-border-strong,#505050)] bg-[var(--mova-ai-hover,#2d2d2d)]\" },\n in_progress: { color: \"var(--mova-ai-info)\", label: \"In progress\", lane: \"border-[var(--mova-ai-border,#3b3b3b)] bg-[var(--mova-ai-canvas,#202020)]\", laneOver: \"border-[var(--mova-ai-border-strong,#505050)] bg-[var(--mova-ai-hover,#2d2d2d)]\" },\n in_review: { color: \"var(--mova-ai-warning)\", label: \"In review\", lane: \"border-[var(--mova-ai-border,#3b3b3b)] bg-[var(--mova-ai-canvas,#202020)]\", laneOver: \"border-[var(--mova-ai-border-strong,#505050)] bg-[var(--mova-ai-hover,#2d2d2d)]\" },\n blocked: { color: \"var(--mova-ai-danger)\", label: \"Blocked\", lane: \"border-[var(--mova-ai-border,#3b3b3b)] bg-[var(--mova-ai-canvas,#202020)]\", laneOver: \"border-[var(--mova-ai-border-strong,#505050)] bg-[var(--mova-ai-hover,#2d2d2d)]\" },\n done: { color: \"var(--mova-ai-success)\", label: \"Done\", lane: \"border-[var(--mova-ai-border,#3b3b3b)] bg-[var(--mova-ai-canvas,#202020)]\", laneOver: \"border-[var(--mova-ai-border-strong,#505050)] bg-[var(--mova-ai-hover,#2d2d2d)]\" },\n cancelled: { color: \"var(--mova-ai-neutral-status)\", label: \"Cancelled\", lane: \"border-[var(--mova-ai-border,#3b3b3b)] bg-[var(--mova-ai-canvas,#202020)]\", laneOver: \"border-[var(--mova-ai-border-strong,#505050)] bg-[var(--mova-ai-hover,#2d2d2d)]\" },\n};\n\nconst DEFAULT_ISSUES: readonly BoardIssue[] = [\n { id: \"R6Z-141\", title: \"Retire legacy Node backend, CLI, and MCP authorities\", status: \"backlog\", priority: \"high\", assignee: \"Noah\", project: \"Rudder-dev\", labels: [\"Feature\"], created: \"Aug 20\" },\n { id: \"R6Z-140\", title: \"Cut over the public listener and Rust authorities\", status: \"backlog\", priority: \"high\", assignee: \"Avery\", project: \"Rudder-dev\", labels: [\"Feature\"], created: \"Aug 20\" },\n { id: \"R6Z-170\", title: \"Proposal: add history versions and Diff view\", status: \"todo\", priority: \"medium\", assignee: \"Luca\", project: \"Rudder-dev\", labels: [\"Feature\"], created: \"Aug 27\" },\n { id: \"R6Z-166\", title: \"Define the issue event contract for agent handoffs\", status: \"todo\", priority: \"medium\", assignee: \"Mira\", project: \"Rudder-dev\", labels: [\"Spec\"], created: \"Aug 26\" },\n { id: \"R6Z-181\", title: \"Share 500-index issue context through MCP and CLI\", status: \"in_progress\", priority: \"high\", assignee: \"Sana\", project: \"Rudder-dev\", labels: [\"Feature\"], created: \"Aug 30\" },\n { id: \"R6Z-210\", title: \"Re-run the Rust output spool after a capacity failure\", status: \"in_progress\", priority: \"high\", assignee: \"Noah\", reviewer: \"Mira\", project: \"Rudder-dev\", labels: [\"Bug\"], created: \"Sep 14\" },\n { id: \"R6Z-207\", title: \"Remove the loaded Issues hint from the shell\", status: \"done\", priority: \"medium\", assignee: \"Theo\", project: \"Rudder-dev\", labels: [\"Feature\"], created: \"Aug 19\" },\n];\n\nfunction StatusIcon({ status }: { status: BoardStatus }) {\n const common = { stroke: \"currentColor\", strokeWidth: 2, strokeLinecap: \"round\" as const };\n return (\n <span className=\"inline-flex h-4 w-4 shrink-0 items-center justify-center\" data-slot=\"issue-status-icon\" data-status={status} style={{ color: STATUS_META[status].color }} aria-hidden=\"true\">\n <svg viewBox=\"0 0 16 16\" className=\"h-full w-full overflow-visible\" fill=\"none\">\n {status === \"backlog\" ? <circle cx=\"8\" cy=\"8\" r=\"5.5\" {...common} strokeDasharray=\"1 3\" /> : null}\n {status === \"todo\" ? <circle cx=\"8\" cy=\"8\" r=\"5.5\" {...common} /> : null}\n {status === \"in_progress\" ? <><circle cx=\"8\" cy=\"8\" r=\"5.5\" {...common} opacity=\"0.22\" /><path d=\"M8 2.5a5.5 5.5 0 0 1 5.5 5.5\" {...common} strokeWidth=\"2.4\" /><path d=\"M13.5 8a5.5 5.5 0 0 1-5.5 5.5\" {...common} strokeWidth=\"2.4\" opacity=\"0.72\" /></> : null}\n {status === \"in_review\" ? <><circle cx=\"8\" cy=\"8\" r=\"5.5\" {...common} /><circle cx=\"8\" cy=\"8\" r=\"2\" fill=\"currentColor\" /></> : null}\n {status === \"done\" ? <><circle cx=\"8\" cy=\"8\" r=\"6.5\" fill=\"currentColor\" /><path d=\"M4.75 8.25 7 10.5l4.25-5\" stroke=\"var(--mova-ai-status-on,#fff)\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" /></> : null}\n {status === \"cancelled\" ? <><circle cx=\"8\" cy=\"8\" r=\"6\" fill=\"currentColor\" opacity=\"0.16\" /><circle cx=\"8\" cy=\"8\" r=\"5.5\" {...common} /><path d=\"m5.25 5.25 5.5 5.5m0-5.5-5.5 5.5\" {...common} /></> : null}\n {status === \"blocked\" ? <><path d=\"M5.1 2.25h5.8l3.05 3.05v5.4l-3.05 3.05H5.1L2.05 10.7V5.3Z\" {...common} strokeLinejoin=\"round\" /><path d=\"m5.25 5.25 5.5 5.5\" {...common} /></> : null}\n </svg>\n </span>\n );\n}\n\nfunction PriorityIcon({ priority }: { priority: BoardIssue[\"priority\"] }) {\n const active = priority === \"critical\" ? 4 : priority === \"high\" ? 3 : priority === \"medium\" ? 2 : 1;\n const tone = priority === \"critical\" ? \"var(--mova-ai-danger)\" : priority === \"high\" ? \"var(--mova-ai-warning)\" : priority === \"medium\" ? \"var(--mova-ai-info)\" : \"var(--mova-ai-neutral-status)\";\n const heights = [4, 6, 10, 14];\n return (\n <span className=\"inline-flex h-3.5 w-4 items-end gap-[2px]\" style={{ color: tone }} aria-hidden=\"true\">\n {heights.map((height, index) => <span className=\"w-[3px] rounded-[1px] bg-current\" key={height} style={{ height: `${height}px`, opacity: index < active ? 1 : 0.25 }} />)}\n </span>\n );\n}\n\nconst DISPLAY_FIELDS = [\"Identifier\", \"Priority\", \"Assignee\", \"Reviewer\", \"Labels\", \"Project\", \"Updated\", \"Created\"] as const;\ntype DisplayField = typeof DISPLAY_FIELDS[number];\nconst DisplayContext = createContext<readonly DisplayField[]>(DISPLAY_FIELDS.filter((field) => field !== \"Updated\"));\n\nfunction MemberAvatar({ name }: { name: string }) {\n return <span aria-hidden=\"true\" className=\"inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-[var(--mova-ai-hover-strong,#e9e9e9)] text-[9px] font-semibold text-[color:var(--mova-ai-ink,#202020)]\">{name.slice(0, 1)}</span>;\n}\n\nfunction IssueListRow({ issue }: { issue: BoardIssue }) {\n const display = useContext(DisplayContext);\n return <article className=\"rudder-list-row border-b border-[var(--mova-ai-border-soft,#ededed)] px-4 py-3\" data-testid={`issue-row-${issue.id}`}>\n <div className=\"flex min-w-0 items-start gap-2.5\">\n <span className=\"mt-0.5\" title={STATUS_META[issue.status].label}><span className=\"sr-only\">{STATUS_META[issue.status].label}</span><StatusIcon status={issue.status} /></span>\n {display.includes(\"Identifier\") ? <span className=\"shrink-0 pt-0.5 font-mono text-xs text-[color:var(--mova-ai-ink-muted,#858585)]\">{issue.id}</span> : null}\n <h3 className=\"min-w-0 flex-1 break-words text-sm font-medium leading-relaxed text-[color:var(--mova-ai-ink,#202020)]\">{issue.title}</h3>\n </div>\n <div className=\"rudder-list-meta flex min-w-0 flex-wrap items-center gap-x-3 gap-y-2 text-xs text-[color:var(--mova-ai-ink-secondary,#666)]\">\n {display.includes(\"Priority\") ? <span title={`${issue.priority} priority`}><span className=\"sr-only\">{issue.priority} priority</span><PriorityIcon priority={issue.priority} /></span> : null}\n {display.includes(\"Labels\") ? issue.labels.map((label) => <span key={label} className=\"inline-flex max-w-full items-center gap-1.5 rounded-full border border-[var(--mova-ai-border,#dedede)] px-2 py-0.5\"><span className=\"h-1.5 w-1.5 shrink-0 rounded-full bg-[var(--mova-ai-neutral-status,#69737d)]\" /><span className=\"break-words\">{label}</span></span>) : null}\n {display.includes(\"Assignee\") ? <span className=\"inline-flex min-w-0 items-center gap-1.5\"><MemberAvatar name={issue.assignee} /><span className=\"break-words\">{issue.assignee}</span></span> : null}\n {display.includes(\"Reviewer\") && issue.reviewer ? <span className=\"inline-flex items-center gap-1\" title=\"Reviewer\"><UserCheck className=\"h-3 w-3 shrink-0\" />{issue.reviewer}</span> : null}\n {display.includes(\"Project\") ? <span className=\"rudder-list-project break-words\">{issue.project}</span> : null}\n {display.includes(\"Created\") ? <span className=\"whitespace-nowrap\">{issue.created}</span> : null}\n {display.includes(\"Updated\") ? <span className=\"whitespace-nowrap\">Updated {issue.updated ?? issue.created}</span> : null}\n </div>\n </article>;\n}\n\nfunction IssueCardBody({ issue }: { issue: BoardIssue }) {\n const display = useContext(DisplayContext);\n return (\n <div className=\"min-w-0\">\n <div className=\"flex min-w-0 items-start gap-2\">\n <span className=\"min-w-0 flex-1 font-mono text-xs text-[color:var(--mova-ai-ink-muted,#858585)]\">{display.includes(\"Identifier\") ? issue.id : \"\"}</span>\n {display.includes(\"Assignee\") ? <span className=\"inline-flex min-w-0 max-w-[8.5rem] shrink items-center gap-1 overflow-hidden text-xs text-[color:var(--mova-ai-ink-secondary,#666)]\">\n <MemberAvatar name={issue.assignee} />\n <span className=\"truncate\">{issue.assignee}</span>\n </span> : null}\n </div>\n <p className=\"mt-2 break-words text-sm leading-snug text-[color:var(--mova-ai-ink,#202020)]\">{issue.title}</p>\n {display.includes(\"Priority\") ? <div className=\"mt-2 flex min-w-0 items-center gap-1.5 overflow-hidden text-xs text-[color:var(--mova-ai-ink-muted,#858585)]\">\n <PriorityIcon priority={issue.priority} />\n <span className=\"capitalize\">{issue.priority}</span>\n </div> : null}\n <div className=\"mt-2 space-y-1.5 border-t border-[var(--mova-ai-border-soft,#ededed)] pt-2 text-xs text-[color:var(--mova-ai-ink-muted,#858585)]\">\n {display.includes(\"Project\") ? <div className=\"grid min-w-0 grid-cols-[4.5rem_minmax(0,1fr)] items-center gap-2\"><span className=\"inline-flex items-center gap-1.5\"><FolderKanban className=\"h-3 w-3\" />Project</span><span className=\"truncate text-right\">{issue.project}</span></div> : null}\n {display.includes(\"Reviewer\") && issue.reviewer ? <div className=\"grid min-w-0 grid-cols-[4.5rem_minmax(0,1fr)] items-center gap-2\"><span className=\"inline-flex items-center gap-1.5\"><UserCheck className=\"h-3 w-3\" />Reviewer</span><span className=\"truncate text-right\">{issue.reviewer}</span></div> : null}\n {display.includes(\"Labels\") ? <div className=\"grid min-w-0 grid-cols-[4.5rem_minmax(0,1fr)] items-center gap-2\"><span className=\"inline-flex items-center gap-1.5\"><Tags className=\"h-3 w-3\" />Labels</span><span className=\"truncate text-right\"><span className=\"inline-flex items-center gap-1 rounded-full border border-[var(--mova-ai-border,#dedede)] px-1.5 py-0.5 text-[10px]\"><span className=\"h-1.5 w-1.5 rounded-full bg-[var(--mova-ai-neutral-status,#69737d)]\" />{issue.labels[0] ?? \"None\"}</span></span></div> : null}\n {display.includes(\"Created\") ? <div className=\"grid min-w-0 grid-cols-[4.5rem_minmax(0,1fr)] items-center gap-2\"><span className=\"inline-flex items-center gap-1.5\"><CalendarClock className=\"h-3 w-3\" />Created</span><span className=\"truncate text-right\">{issue.created}</span></div> : null}\n {display.includes(\"Updated\") ? <div className=\"grid grid-cols-[4.5rem_minmax(0,1fr)] gap-2\"><span>Updated</span><span className=\"truncate text-right\">{issue.updated ?? issue.created}</span></div> : null}\n </div>\n </div>\n );\n}\n\nfunction SortableIssueCard({ issue }: { issue: BoardIssue }) {\n const { attributes, isDragging, listeners, setNodeRef, transform, transition } = useSortable({ id: issue.id, data: { status: issue.status, type: \"issue\" } });\n return (\n <article\n ref={setNodeRef}\n style={{ transform: CSS.Transform.toString(transform), transition }}\n {...attributes}\n {...listeners}\n className={`motion-kanban-card cursor-grab touch-pan-x overflow-hidden rounded-[9px] border border-[var(--mova-ai-border,#dedede)] bg-[var(--mova-ai-surface,#fff)] p-2.5 outline-none transition-[box-shadow,opacity,border-color] motion-reduce:transition-none focus-visible:border-[var(--mova-ai-border-strong,#c8c8c8)] focus-visible:ring-2 focus-visible:ring-[var(--mova-ai-border-strong,#c8c8c8)] active:cursor-grabbing ${isDragging ? \"opacity-25\" : \"hover:border-[var(--mova-ai-border-strong,#c8c8c8)] hover:shadow-sm\"}`}\n data-dragging={isDragging ? \"true\" : \"false\"}\n data-issue-id={issue.id}\n data-testid={`kanban-card-${issue.id}`}\n >\n <IssueCardBody issue={issue} />\n </article>\n );\n}\n\nfunction BoardColumn({ issues, status, onCreate }: { issues: BoardIssue[]; status: BoardStatus; onCreate: (status: BoardStatus, trigger: HTMLElement) => void }) {\n const { isOver, setNodeRef } = useDroppable({ id: status, data: { status, type: \"column\" } });\n const meta = STATUS_META[status];\n return (\n <div className=\"flex min-h-0 w-[260px] min-w-[260px] shrink-0 flex-col\">\n <div className=\"mb-1 flex items-center gap-2 px-2 py-2\">\n <StatusIcon status={status} />\n <span className=\"text-xs font-semibold uppercase tracking-wide text-[color:var(--mova-ai-ink-secondary,#666)]\">{meta.label}</span>\n <span className=\"ml-auto text-xs tabular-nums text-[color:var(--mova-ai-ink-muted,#858585)]\">{issues.length}</span>\n <button type=\"button\" onClick={(event) => onCreate(status, event.currentTarget)} aria-label={`Create issue in ${meta.label}`} className=\"rounded p-1 hover:bg-[var(--mova-ai-hover-strong,#e9e9e9)] focus-visible:ring-2 focus-visible:ring-[var(--mova-ai-border-strong,#c8c8c8)]\"><Plus className=\"h-3.5 w-3.5 text-[color:var(--mova-ai-ink-muted,#858585)]\" aria-hidden=\"true\" /></button>\n </div>\n <div\n ref={setNodeRef}\n className={`motion-kanban-lane min-h-[160px] flex-1 space-y-1.5 overflow-y-auto rounded-[9px] border p-1.5 transition-colors motion-reduce:transition-none ${isOver ? meta.laneOver : meta.lane}`}\n data-over={isOver ? \"true\" : \"false\"}\n data-status={status}\n style={{ \"--lane-status\": meta.color } as CSSProperties}\n data-testid={`kanban-column-${status}`}\n >\n <SortableContext items={issues.map((issue) => issue.id)} strategy={verticalListSortingStrategy}>\n {issues.map((issue) => <Fragment key={issue.id}><SortableIssueCard issue={issue} /></Fragment>)}\n </SortableContext>\n </div>\n </div>\n );\n}\n\nfunction HiddenStatus({ status, onCreate }: { status: BoardStatus; onCreate: (status: BoardStatus, trigger: HTMLElement) => void }) {\n const { isOver, setNodeRef } = useDroppable({ id: status, data: { status, type: \"hidden-column\" } });\n const meta = STATUS_META[status];\n return (\n <div ref={setNodeRef} className={`motion-kanban-lane flex items-center gap-2 rounded-[9px] border px-2 py-2 transition-colors motion-reduce:transition-none ${isOver ? meta.laneOver : meta.lane}`} data-over={isOver ? \"true\" : \"false\"} data-status={status} style={{ \"--lane-status\": meta.color } as CSSProperties} data-testid={`kanban-hidden-column-${status}`}>\n <StatusIcon status={status} />\n <span className=\"min-w-0 flex-1 text-xs font-semibold uppercase tracking-wide text-[color:var(--mova-ai-ink-secondary,#666)]\">{meta.label}</span>\n <span className=\"text-xs tabular-nums text-[color:var(--mova-ai-ink-muted,#858585)]\">0</span>\n <button type=\"button\" onClick={(event) => onCreate(status, event.currentTarget)} aria-label={`Create issue in ${meta.label}`} className=\"rounded p-1 hover:bg-[var(--mova-ai-hover-strong,#e9e9e9)] focus-visible:ring-2 focus-visible:ring-[var(--mova-ai-border-strong,#c8c8c8)]\"><Plus className=\"h-3.5 w-3.5 text-[color:var(--mova-ai-ink-muted,#858585)]\" aria-hidden=\"true\" /></button>\n </div>\n );\n}\n\nfunction ToolbarPopover({ label, icon, children, triggerRef }: { label: string; icon: ReactNode; children: ReactNode; triggerRef?: RefObject<HTMLButtonElement | null> }) {\n const reducedMotion = useReducedMotion();\n const [open, setOpen] = useState(false);\n const root = useRef<HTMLDivElement>(null);\n const trigger = useRef<HTMLButtonElement>(null);\n const panel = useRef<HTMLDivElement>(null);\n const id = useId();\n useLayoutEffect(() => {\n if (!open) return;\n const container = root.current;\n const popup = panel.current;\n const board = container?.closest<HTMLElement>('section[aria-label=\"Rudder issue board\"]');\n if (!container || !popup || !board) return;\n const position = () => {\n const anchor = container.getBoundingClientRect();\n const bounds = board.getBoundingClientRect();\n // Convert viewport measurements back to local CSS pixels for scaled previews.\n const scale = container.offsetWidth ? anchor.width / container.offsetWidth : 1;\n if (!scale) return;\n const inset = 8 * scale;\n popup.style.maxWidth = `${Math.max(0, (bounds.width - inset * 2) / scale)}px`;\n const width = popup.getBoundingClientRect().width;\n const left = Math.max(bounds.left + inset, Math.min(anchor.right - width, bounds.right - inset - width));\n popup.style.left = `${(left - anchor.left) / scale}px`;\n popup.style.right = \"auto\";\n };\n position();\n const observer = new ResizeObserver(position);\n observer.observe(board);\n observer.observe(container);\n window.addEventListener(\"resize\", position);\n return () => { observer.disconnect(); window.removeEventListener(\"resize\", position); };\n }, [open]);\n useEffect(() => {\n if (!open) return;\n panel.current?.querySelector<HTMLElement>(\"input:checked, button, input\")?.focus();\n const dismiss = (event: PointerEvent) => {\n if (!root.current?.contains(event.target as Node)) { setOpen(false); trigger.current?.focus(); }\n };\n document.addEventListener(\"pointerdown\", dismiss);\n return () => document.removeEventListener(\"pointerdown\", dismiss);\n }, [open]);\n return <div ref={root} className=\"relative\" onBlur={(event) => { if (event.relatedTarget && !event.currentTarget.contains(event.relatedTarget as Node)) setOpen(false); }} onKeyDown={(event) => { if (event.key === \"Escape\" && open) { event.preventDefault(); event.stopPropagation(); setOpen(false); trigger.current?.focus(); } }}>\n <button ref={(node) => { trigger.current = node; if (triggerRef) triggerRef.current = node; }} type=\"button\" aria-label={label === \"Sort\" ? \"Sort issues\" : label === \"Filter\" ? \"Filter issues by status\" : \"Display fields\"} aria-expanded={open} aria-controls={id} onClick={() => setOpen((value) => !value)} className={`flex items-center gap-1.5 rounded-md px-2 py-1.5 transition-colors hover:bg-[var(--mova-ai-hover,#f1f1f1)] ${open ? \"bg-[var(--mova-ai-hover-strong,#e9e9e9)] text-[color:var(--mova-ai-ink,#202020)]\" : \"\"}`}>{icon}{label}<ChevronDown className=\"h-3 w-3\" /></button>\n <AnimatePresence initial={false}>{open ? <motion.div key=\"panel\" ref={panel} id={id} role=\"group\" aria-label={`${label} options`} initial={{ opacity: 0, y: reducedMotion ? 0 : -4 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: reducedMotion ? 0 : -3 }} transition={{ duration: reducedMotion ? 0 : 0.16, ease: [0.22, 1, 0.36, 1] }} className=\"absolute right-0 z-30 mt-2 w-48 space-y-1 rounded-lg border border-[var(--mova-ai-border,#dedede)] bg-[var(--mova-ai-surface,#fff)] p-2 text-xs shadow-xl\">{children}</motion.div> : null}</AnimatePresence>\n </div>;\n}\n\nfunction resolveTargetStatus(overId: string, issues: BoardIssue[]): BoardStatus | null {\n if (BOARD_STATUSES.includes(overId as BoardStatus)) return overId as BoardStatus;\n return issues.find((issue) => issue.id === overId)?.status ?? null;\n}\n\nexport function RudderIssueBoard({ theme = \"dark\", className = \"\", initialIssues = DEFAULT_ISSUES, onIssuesChange }: RudderIssueBoardProps) {\n const rootRef = useRef<HTMLElement>(null);\n const sortGroupId = useId();\n const keyboardTargetRef = useRef<BoardStatus | null>(null);\n const [issues, setIssues] = useState<BoardIssue[]>(() => [...initialIssues]);\n const [statusFilter, setStatusFilter] = useState(\"all\");\n const [sort, setSort] = useState(\"Manual\");\n const [descending, setDescending] = useState(false);\n const [view, setView] = useState<\"board\" | \"list\">(\"board\");\n const [display, setDisplay] = useState<DisplayField[]>(DISPLAY_FIELDS.filter((field) => field !== \"Updated\"));\n const [creating, setCreating] = useState<BoardStatus | null>(null);\n const [draft, setDraft] = useState(\"\");\n const createInputRef = useRef<HTMLInputElement>(null);\n const createTriggerRef = useRef<HTMLElement | null>(null);\n const listCreateRef = useRef<HTMLButtonElement>(null);\n const statusFilterRef = useRef<HTMLButtonElement>(null);\n const createOpen = creating !== null;\n const restoreCreateFocus = () => requestAnimationFrame(() => {\n const original = createTriggerRef.current;\n (original?.isConnected ? original : listCreateRef.current ?? statusFilterRef.current)?.focus();\n });\n const openCreate = (status: BoardStatus, trigger: HTMLElement) => { createTriggerRef.current = trigger; setCreating(status); setDraft(\"\"); };\n const closeCreate = () => { setCreating(null); restoreCreateFocus(); };\n useEffect(() => { if (createOpen) createInputRef.current?.focus(); }, [createOpen]);\n const orderedIssues = useMemo(() => {\n const matches = issues;\n if (sort === \"Manual\") return matches;\n const dateValue = (value: string): string | number => { const timestamp = Date.parse(value); return Number.isNaN(timestamp) ? value : timestamp; };\n return [...matches].sort((a, b) => {\n const value = (issue: BoardIssue): string | number => sort === \"Status\" ? BOARD_STATUSES.indexOf(issue.status) : sort === \"Priority\" ? [\"critical\", \"high\", \"medium\", \"low\"].indexOf(issue.priority) : sort === \"Title\" ? issue.title : dateValue(sort === \"Updated\" ? issue.updated ?? issue.created : issue.created);\n const av = value(a), bv = value(b);\n const delta = typeof av === \"number\" && typeof bv === \"number\" ? av - bv : String(av).localeCompare(String(bv), undefined, { numeric: true });\n return descending ? -delta : delta;\n });\n }, [issues, sort, descending]);\n const filtered = useMemo(() => orderedIssues.filter((issue) => statusFilter === \"all\" || issue.status === statusFilter), [orderedIssues, statusFilter]);\n const [activeId, setActiveId] = useState<string | null>(null);\n const [overStatus, setOverStatus] = useState<BoardStatus | null>(null);\n const [announcement, setAnnouncement] = useState(\"Issue board ready. Pick up a card to move it between status columns.\");\n const [reduceMotion, setReduceMotion] = useState(false);\n const keyboardCoordinates = useMemo(\n () => createBoardKeyboardCoordinates(\n () => keyboardTargetRef.current,\n (status) => { keyboardTargetRef.current = status; },\n ),\n [],\n );\n const sensors = useSensors(\n useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),\n useSensor(KeyboardSensor, { coordinateGetter: keyboardCoordinates }),\n );\n const grouped = useMemo(() => Object.fromEntries(BOARD_STATUSES.map((status) => [status, filtered.filter((issue) => issue.status === status)])) as Record<BoardStatus, BoardIssue[]>, [filtered]);\n const visibleStatuses = BOARD_STATUSES.filter((status) => grouped[status].length > 0);\n const hiddenStatuses = BOARD_STATUSES.filter((status) => grouped[status].length === 0);\n const activeIssue = activeId ? issues.find((issue) => issue.id === activeId) ?? null : null;\n\n useEffect(() => {\n const media = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n const update = () => setReduceMotion(media.matches);\n update();\n media.addEventListener(\"change\", update);\n return () => media.removeEventListener(\"change\", update);\n }, []);\n\n const collisionDetection: CollisionDetection = (args) => {\n if (!args.pointerCoordinates && keyboardTargetRef.current) {\n const target = args.droppableContainers.find((container) => container.id === keyboardTargetRef.current);\n if (target) return [{ id: target.id, data: { droppableContainer: target, value: 0 } }];\n }\n const pointerCollisions = pointerWithin(args);\n if (pointerCollisions.length > 0) {\n const issueCollision = pointerCollisions.find(({ id }) =>\n args.droppableContainers.find((container) => container.id === id)?.data.current?.type === \"issue\"\n );\n if (issueCollision) return [issueCollision];\n\n const laneCollision = pointerCollisions.find(({ id }) => {\n const type = args.droppableContainers.find((container) => container.id === id)?.data.current?.type;\n return type === \"column\" || type === \"hidden-column\";\n });\n const lane = laneCollision\n ? args.droppableContainers.find((container) => container.id === laneCollision.id)\n : null;\n const targetStatus = lane?.data.current?.status as BoardStatus | undefined;\n const laneIssues = targetStatus\n ? args.droppableContainers.filter((container) =>\n container.id !== args.active.id\n && container.data.current?.type === \"issue\"\n && container.data.current?.status === targetStatus\n )\n : [];\n const laneIssueBottom = Math.max(\n ...laneIssues.map((container) => args.droppableRects.get(container.id)?.bottom ?? Number.NEGATIVE_INFINITY),\n );\n if (args.pointerCoordinates && args.pointerCoordinates.y <= laneIssueBottom) {\n const nearestIssue = closestCenter({ ...args, droppableContainers: laneIssues })[0];\n if (nearestIssue) return [nearestIssue];\n }\n\n return pointerCollisions;\n }\n return closestCenter(args);\n };\n\n const clearDrag = (event?: DragCancelEvent) => {\n keyboardTargetRef.current = null;\n setActiveId(null);\n setOverStatus(null);\n if (event) setAnnouncement(\"Move cancelled.\");\n };\n\n const handleDragStart = ({ active }: DragStartEvent) => {\n const issue = issues.find((candidate) => candidate.id === String(active.id));\n setActiveId(String(active.id));\n if (issue) setAnnouncement(`Picked up ${issue.id}. Current status: ${STATUS_META[issue.status].label}.`);\n };\n\n const handleDragOver = ({ over }: DragOverEvent) => {\n setOverStatus(over ? resolveTargetStatus(String(over.id), issues) : null);\n };\n\n const handleDragEnd = ({ active, over }: DragEndEvent) => {\n clearDrag();\n if (!over) {\n setAnnouncement(\"Move cancelled.\");\n return;\n }\n const activeIssueId = String(active.id);\n const overId = String(over.id);\n const targetStatus = resolveTargetStatus(overId, issues);\n const movingIssue = issues.find((issue) => issue.id === activeIssueId);\n if (!targetStatus || !movingIssue) return;\n\n const sourceStatus = movingIssue.status;\n const allGrouped = Object.fromEntries(BOARD_STATUSES.map((status) => [status, orderedIssues.filter((issue) => issue.status === status)])) as Record<BoardStatus, BoardIssue[]>;\n const targetLaneBeforeMove = allGrouped[targetStatus];\n const nextByStatus = Object.fromEntries(BOARD_STATUSES.map((status) => [status, allGrouped[status].filter((issue) => issue.id !== activeIssueId)])) as Record<BoardStatus, BoardIssue[]>;\n const targetLane = nextByStatus[targetStatus];\n const overIndex = targetLaneBeforeMove.findIndex((issue) => issue.id === overId);\n const insertAt = overIndex >= 0 ? Math.min(overIndex, targetLane.length) : targetLane.length;\n targetLane.splice(insertAt, 0, { ...movingIssue, status: targetStatus });\n const nextIssues = BOARD_STATUSES.flatMap((status) => nextByStatus[status]);\n\n if (sourceStatus !== targetStatus || nextIssues.map((issue) => issue.id).join(\"|\") !== issues.map((issue) => issue.id).join(\"|\")) {\n setSort(\"Manual\");\n setIssues(nextIssues);\n onIssuesChange?.(nextIssues);\n setAnnouncement(`${movingIssue.id} moved to ${STATUS_META[targetStatus].label}.`);\n requestAnimationFrame(() => requestAnimationFrame(() => {\n const movedCard = Array.from(rootRef.current?.querySelectorAll<HTMLElement>(\"[data-issue-id]\") ?? [])\n .find((card) => card.dataset.issueId === activeIssueId);\n (movedCard ?? statusFilterRef.current)?.focus({ preventScroll: true });\n }));\n } else {\n setAnnouncement(`${movingIssue.id} stayed in ${STATUS_META[targetStatus].label}.`);\n }\n };\n\n return (\n <section ref={rootRef} data-theme={theme} className={`mova-ai-theme rudder-board w-full min-w-0 min-h-[620px] bg-[var(--mova-ai-canvas,#202020)] text-[color:var(--mova-ai-ink,#eeeeee)] ${className}`} aria-label=\"Rudder issue board\">\n <style>{AI_THEME_STYLES}{`\n .rudder-board :is(button,input,select,summary):focus-visible { outline: 2px solid var(--mova-ai-accent,#c2c2c2); outline-offset: 2px; }\n .rudder-board { container-type: inline-size; }\n .rudder-board .motion-kanban-lane[data-status] { position: relative; overflow: hidden; }\n .rudder-board .motion-kanban-lane[data-status]::after { content: \"\"; position: absolute; inset-inline: 7px; bottom: 0; height: 2px; border-radius: 999px; background: var(--lane-status); opacity: .82; pointer-events: none; }\n .rudder-board .motion-kanban-lane[data-status][data-over=\"true\"]::after { opacity: 1; height: 3px; }\n .rudder-list-row { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 40%); align-items: start; gap: 16px; }\n .rudder-list-meta { justify-content: flex-end; padding-top: 2px; }\n @container (max-width: 640px) {\n .rudder-list-row { grid-template-columns: minmax(0, 1fr); gap: 8px; }\n .rudder-list-meta { justify-content: flex-start; padding-left: 26px; }\n }\n @media (prefers-reduced-motion: reduce) {\n .motion-kanban-card,\n .motion-kanban-lane {\n animation: none !important;\n transition: none !important;\n }\n }\n `}</style>\n <p className=\"sr-only\" aria-live=\"assertive\">{announcement}</p>\n <div className=\"flex flex-wrap items-center gap-2 border-b border-[var(--mova-ai-border,#dedede)] px-4 py-3 text-xs text-[color:var(--mova-ai-ink-secondary,#666)]\">\n <h2 className=\"mr-auto text-xs font-medium uppercase tracking-[0.16em] text-[color:var(--mova-ai-ink-secondary,#666)]\">Issues</h2>\n <div className=\"flex flex-wrap items-center justify-end gap-1\">\n <button type=\"button\" aria-label=\"Show working issues\" aria-pressed={statusFilter === \"in_progress\"} onClick={() => setStatusFilter((value) => value === \"in_progress\" ? \"all\" : \"in_progress\")} className={`mr-1 rounded-md border border-[var(--mova-ai-border,#dedede)] px-2.5 py-1.5 tabular-nums transition-colors hover:bg-[var(--mova-ai-hover,#f1f1f1)] ${statusFilter === \"in_progress\" ? \"bg-[var(--mova-ai-hover-strong,#e9e9e9)] text-[color:var(--mova-ai-ink,#202020)]\" : \"\"}`}>{issues.filter((issue) => issue.status === \"in_progress\").length} working</button>\n <div className=\"mr-1 flex rounded-md border border-[var(--mova-ai-border,#dedede)] p-0.5\"><button type=\"button\" aria-label=\"List view\" aria-pressed={view === \"list\"} onClick={() => setView(\"list\")} className={`rounded p-1 transition-colors ${view === \"list\" ? \"bg-[var(--mova-ai-hover-strong,#e9e9e9)] text-[color:var(--mova-ai-ink,#202020)]\" : \"\"}`}><List className=\"h-3.5 w-3.5\" /></button><button type=\"button\" aria-label=\"Board view\" aria-pressed={view === \"board\"} onClick={() => setView(\"board\")} className={`rounded p-1 transition-colors ${view === \"board\" ? \"bg-[var(--mova-ai-hover-strong,#e9e9e9)] text-[color:var(--mova-ai-ink,#202020)]\" : \"\"}`}><Columns3 className=\"h-3.5 w-3.5\" /></button></div>\n <ToolbarPopover label=\"Filter\" triggerRef={statusFilterRef} icon={<Filter className=\"h-3 w-3\" />}>\n <div role=\"radiogroup\" aria-label=\"Issue status\">{[\"all\", ...BOARD_STATUSES].map((status) => <label key={status} className=\"flex cursor-pointer items-center gap-2 rounded px-2 py-1.5 hover:bg-[var(--mova-ai-hover,#f1f1f1)]\"><input type=\"radio\" className=\"accent-[var(--mova-ai-accent,#454545)]\" name={`${sortGroupId}-status`} checked={statusFilter === status} onChange={() => setStatusFilter(status)} />{status === \"all\" ? \"All statuses\" : STATUS_META[status as BoardStatus].label}</label>)}</div>\n </ToolbarPopover>\n <ToolbarPopover label=\"Sort\" icon={<ArrowUpDown className=\"h-3 w-3\" />}>\n <div role=\"radiogroup\" aria-label=\"Sort field\">{[\"Manual\", \"Status\", \"Priority\", \"Title\", \"Created\", \"Updated\"].map((value) => <label key={value} className={`flex cursor-pointer items-center gap-2 rounded px-2 py-1.5 hover:bg-[var(--mova-ai-hover,#f1f1f1)] ${sort === value ? \"bg-[var(--mova-ai-hover,#f1f1f1)] text-[color:var(--mova-ai-ink,#202020)]\" : \"\"}`}><input className=\"accent-[var(--mova-ai-accent,#454545)]\" type=\"radio\" name={sortGroupId} checked={sort === value} onChange={() => setSort(value)} />{value}{sort === value ? <Check className=\"ml-auto h-3 w-3\" /> : null}</label>)}</div>\n <button type=\"button\" disabled={sort === \"Manual\"} onClick={() => setDescending((value) => !value)} aria-label=\"Toggle sort direction\" className=\"flex w-full items-center gap-2 border-t border-[var(--mova-ai-border,#dedede)] px-2 py-2 text-left disabled:opacity-40\"><ArrowUpDown className=\"h-3 w-3\" />{descending ? \"Descending\" : \"Ascending\"}</button>\n </ToolbarPopover>\n <ToolbarPopover label=\"Display\" icon={<SlidersHorizontal className=\"h-3 w-3\" />}>{DISPLAY_FIELDS.map((field) => <label key={field} className=\"flex cursor-pointer items-center gap-2 rounded px-2 py-1.5 hover:bg-[var(--mova-ai-hover,#f1f1f1)]\"><input className=\"accent-[var(--mova-ai-accent,#454545)]\" type=\"checkbox\" checked={display.includes(field)} onChange={() => setDisplay((fields) => fields.includes(field) ? fields.filter((value) => value !== field) : [...fields, field])} />{field}</label>)}</ToolbarPopover>\n </div>\n </div>\n {creating ? <form onKeyDown={(event) => { if (event.key === \"Escape\") { event.preventDefault(); closeCreate(); } }} className=\"flex flex-wrap items-center gap-2 border-b border-[var(--mova-ai-border,#dedede)] bg-[var(--mova-ai-hover,#f1f1f1)] p-3\" onSubmit={(event) => { event.preventDefault(); if (!draft.trim()) return; let sequence = issues.length + 1; while (issues.some((issue) => issue.id === `ISS-${sequence}`)) sequence++; const next = [...issues, { id: `ISS-${sequence}`, title: draft.trim(), status: creating, priority: \"medium\" as const, assignee: \"Unassigned\", project: \"No project\", labels: [], created: new Date().toISOString().slice(0, 10) }]; setIssues(next); onIssuesChange?.(next); setStatusFilter(\"all\"); closeCreate(); setAnnouncement(\"Issue created.\"); }}><input ref={createInputRef} required aria-label=\"New issue title\" placeholder=\"Issue title\" value={draft} onChange={(event) => setDraft(event.target.value)} className=\"min-w-0 flex-1 rounded border border-[var(--mova-ai-border,#dedede)] bg-[var(--mova-ai-field,#f6f6f6)] px-2 py-1.5 text-sm\" /><select aria-label=\"New issue status\" value={creating} onChange={(event) => setCreating(event.target.value as BoardStatus)} className=\"rounded bg-[var(--mova-ai-canvas,#f4f4f4)] p-1 text-xs\">{BOARD_STATUSES.map((status) => <option key={status} value={status}>{STATUS_META[status].label}</option>)}</select><button type=\"submit\" className=\"rounded bg-[var(--mova-ai-accent,#454545)] px-3 py-1.5 text-xs text-[color:var(--mova-ai-accent-foreground,#fff)]\">Create</button><button type=\"button\" onClick={closeCreate} className=\"px-2 text-xs\">Cancel</button></form> : null}\n <DisplayContext.Provider value={display}>\n <AnimatePresence mode=\"wait\" initial={false}>\n <motion.div key={view} initial={{ opacity: 0, y: reduceMotion ? 0 : 5 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: reduceMotion ? 0 : -3 }} transition={{ duration: reduceMotion ? 0 : 0.15, ease: [0.22, 1, 0.36, 1] }}>\n {view === \"list\" ? <div className=\"max-h-[580px] overflow-y-auto\">{filtered.map((issue) => <IssueListRow key={issue.id} issue={issue} />)}{filtered.length === 0 ? <p className=\"py-10 text-center text-sm text-[color:var(--mova-ai-ink-secondary,#666)]\">No issues match this filter.</p> : null}<button ref={listCreateRef} type=\"button\" onClick={(event) => openCreate(statusFilter === \"all\" ? \"backlog\" : statusFilter as BoardStatus, event.currentTarget)} className=\"flex items-center gap-2 px-4 py-3 text-xs text-[color:var(--mova-ai-ink-secondary,#666)] transition-colors hover:text-[color:var(--mova-ai-ink,#202020)]\"><Plus className=\"h-3.5 w-3.5\" />Add issue</button></div> :\n <DndContext\n autoScroll={{ acceleration: 2, interval: 10, threshold: { x: 0.08, y: 0.2 } }}\n collisionDetection={collisionDetection}\n onDragCancel={clearDrag}\n onDragEnd={handleDragEnd}\n onDragOver={handleDragOver}\n onDragStart={handleDragStart}\n sensors={sensors}\n >\n <div className=\"overflow-x-auto overscroll-x-contain p-3 sm:p-4\">\n <div className=\"flex min-h-[520px] min-w-max items-stretch gap-3\">\n {visibleStatuses.map((status) => <Fragment key={status}><BoardColumn issues={grouped[status]} status={status} onCreate={openCreate} /></Fragment>)}\n {hiddenStatuses.length > 0 ? (\n <div className={`motion-kanban-lane flex min-h-0 w-[228px] min-w-[228px] shrink-0 flex-col rounded-[10px] border p-2 transition-colors motion-reduce:transition-none ${overStatus && hiddenStatuses.includes(overStatus) ? \"border-[var(--mova-ai-border-strong,#c8c8c8)] bg-[var(--mova-ai-hover,#f1f1f1)]\" : \"border-[var(--mova-ai-border,#dedede)] bg-[var(--mova-ai-canvas,#f4f4f4)]\"}`} data-testid=\"kanban-hidden-columns\">\n <div className=\"mb-2 flex items-center gap-2 px-1\">\n <span className=\"text-xs font-semibold uppercase tracking-wide text-[color:var(--mova-ai-ink-muted,#858585)]\">Hidden columns</span>\n <span className=\"ml-auto text-xs tabular-nums text-[color:var(--mova-ai-ink-muted,#858585)]\">{hiddenStatuses.length}</span>\n </div>\n <div className=\"space-y-2\">{hiddenStatuses.map((status) => <Fragment key={status}><HiddenStatus status={status} onCreate={openCreate} /></Fragment>)}</div>\n </div>\n ) : null}\n </div>\n </div>\n <DragOverlay dropAnimation={reduceMotion ? null : { duration: 180, easing: \"cubic-bezier(0.22, 1, 0.36, 1)\" }}>\n {activeIssue ? <article className=\"w-[247px] rotate-[0.4deg] scale-[1.015] overflow-hidden rounded-[9px] border border-[var(--mova-ai-border-strong,#c8c8c8)] bg-[var(--mova-ai-surface,#fff)] p-2.5 shadow-[0_18px_48px_rgba(0,0,0,.48)]\"><IssueCardBody issue={activeIssue} /></article> : null}\n </DragOverlay>\n </DndContext>}\n </motion.div>\n </AnimatePresence>\n </DisplayContext.Provider>\n </section>\n );\n}\n\nexport default RudderIssueBoard;\n"
24
24
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "sales-analytics-dual-bars",
3
3
  "tier": "free",
4
- "version": "0.4.4",
4
+ "version": "0.4.5",
5
5
  "file": "sales-analytics-dual-bars.tsx",
6
6
  "registryFile": "sales-analytics-dual-bars.json",
7
7
  "exports": [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "sales-overview-radial-dashboard",
3
3
  "tier": "free",
4
- "version": "0.4.4",
4
+ "version": "0.4.5",
5
5
  "file": "sales-overview-radial-dashboard.tsx",
6
6
  "registryFile": "sales-overview-radial-dashboard.json",
7
7
  "exports": [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "sales-target-segmented-arc",
3
3
  "tier": "free",
4
- "version": "0.4.4",
4
+ "version": "0.4.5",
5
5
  "file": "sales-target-segmented-arc.tsx",
6
6
  "registryFile": "sales-target-segmented-arc.json",
7
7
  "exports": [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "scale-in-char",
3
3
  "tier": "free",
4
- "version": "0.4.4",
4
+ "version": "0.4.5",
5
5
  "file": "scale-in-char.tsx",
6
6
  "registryFile": "scale-in-char.json",
7
7
  "exports": [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "scale-in-text",
3
3
  "tier": "free",
4
- "version": "0.4.4",
4
+ "version": "0.4.5",
5
5
  "file": "scale-in-text.tsx",
6
6
  "registryFile": "scale-in-text.json",
7
7
  "exports": [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "scale-in-word",
3
3
  "tier": "free",
4
- "version": "0.4.4",
4
+ "version": "0.4.5",
5
5
  "file": "scale-in-word.tsx",
6
6
  "registryFile": "scale-in-word.json",
7
7
  "exports": [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "scale-in",
3
3
  "tier": "free",
4
- "version": "0.4.4",
4
+ "version": "0.4.5",
5
5
  "file": "scale-in.tsx",
6
6
  "sourcePath": "ui/entrance/scale-in.tsx",
7
7
  "registryFile": "scale-in.json",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "scramble-text",
3
3
  "tier": "free",
4
- "version": "0.4.4",
4
+ "version": "0.4.5",
5
5
  "file": "scramble-text.tsx",
6
6
  "registryFile": "scramble-text.json",
7
7
  "exports": [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "scroll-canvas-unroll",
3
3
  "tier": "free",
4
- "version": "0.4.4",
4
+ "version": "0.4.5",
5
5
  "file": "scroll-canvas-unroll.tsx",
6
6
  "sourcePath": "components/css-animations/whimsical/ConceptTrios2.tsx",
7
7
  "registryFile": "scroll-canvas-unroll.json",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "scroll-reveal",
3
3
  "tier": "free",
4
- "version": "0.4.4",
4
+ "version": "0.4.5",
5
5
  "file": "scroll-reveal.tsx",
6
6
  "sourcePath": "ui/scroll/scroll-reveal.tsx",
7
7
  "registryFile": "scroll-reveal.json",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "search-expand",
3
3
  "tier": "free",
4
- "version": "0.4.4",
4
+ "version": "0.4.5",
5
5
  "file": "search-expand.tsx",
6
6
  "registryFile": "search-expand.json",
7
7
  "exports": [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "segmented-arc-meter",
3
3
  "tier": "free",
4
- "version": "0.4.4",
4
+ "version": "0.4.5",
5
5
  "file": "segmented-arc-meter.tsx",
6
6
  "sourcePath": "components/css-animations/yui-components/RedesignedUiTrios.tsx",
7
7
  "registryFile": "segmented-arc-meter.json",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "segmented-link-stretch",
3
3
  "tier": "free",
4
- "version": "0.4.4",
4
+ "version": "0.4.5",
5
5
  "file": "segmented-link-stretch.tsx",
6
6
  "sourcePath": "components/css-animations/whimsical/ConceptTrios3.tsx",
7
7
  "registryFile": "segmented-link-stretch.json",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "segmented-step-bar",
3
3
  "tier": "free",
4
- "version": "0.4.4",
4
+ "version": "0.4.5",
5
5
  "file": "segmented-step-bar.tsx",
6
6
  "sourcePath": "components/css-animations/yui-components/UiKitTrios.tsx",
7
7
  "registryFile": "segmented-step-bar.json",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "segmented-stepper-dots",
3
3
  "tier": "free",
4
- "version": "0.4.4",
4
+ "version": "0.4.5",
5
5
  "file": "segmented-stepper-dots.tsx",
6
6
  "sourcePath": "components/css-animations/yui-components/RedesignedUiTrios.tsx",
7
7
  "registryFile": "segmented-stepper-dots.json",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "select-dropdown",
3
3
  "tier": "free",
4
- "version": "0.4.4",
4
+ "version": "0.4.5",
5
5
  "file": "select-dropdown.tsx",
6
6
  "registryFile": "select-dropdown.json",
7
7
  "exports": [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "server-performance-step-bars",
3
3
  "tier": "free",
4
- "version": "0.4.4",
4
+ "version": "0.4.5",
5
5
  "file": "server-performance-step-bars.tsx",
6
6
  "registryFile": "server-performance-step-bars.json",
7
7
  "exports": [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "shape-shift-grid",
3
3
  "tier": "free",
4
- "version": "0.4.4",
4
+ "version": "0.4.5",
5
5
  "file": "shape-shift-grid.tsx",
6
6
  "sourcePath": "ui/loading/shape-shift-grid.tsx",
7
7
  "registryFile": "shape-shift-grid.json",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "shimmer-line",
3
3
  "tier": "free",
4
- "version": "0.4.4",
4
+ "version": "0.4.5",
5
5
  "file": "shimmer-line.tsx",
6
6
  "sourcePath": "ui/loading/shimmer-line.tsx",
7
7
  "registryFile": "shimmer-line.json",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "shimmer-text",
3
3
  "tier": "free",
4
- "version": "0.4.4",
4
+ "version": "0.4.5",
5
5
  "file": "shimmer-text.tsx",
6
6
  "registryFile": "shimmer-text.json",
7
7
  "exports": [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "shutter-slide",
3
3
  "tier": "free",
4
- "version": "0.4.4",
4
+ "version": "0.4.5",
5
5
  "file": "shutter-slide.tsx",
6
6
  "sourcePath": "components/css-animations/whimsical/ConceptTrios1.tsx",
7
7
  "registryFile": "shutter-slide.json",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "shutter-step-slices",
3
3
  "tier": "free",
4
- "version": "0.4.4",
4
+ "version": "0.4.5",
5
5
  "file": "shutter-step-slices.tsx",
6
6
  "sourcePath": "components/css-animations/whimsical/YuiPhysicsExperiments.tsx",
7
7
  "registryFile": "shutter-step-slices.json",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "siri-wave",
3
3
  "tier": "free",
4
- "version": "0.4.4",
4
+ "version": "0.4.5",
5
5
  "file": "siri-wave.tsx",
6
6
  "sourcePath": "ui/loading/siri-wave.tsx",
7
7
  "registryFile": "siri-wave.json",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "skeleton-loader",
3
3
  "tier": "free",
4
- "version": "0.4.4",
4
+ "version": "0.4.5",
5
5
  "file": "skeleton-loader.tsx",
6
6
  "sourcePath": "ui/loading/skeleton-loader.tsx",
7
7
  "registryFile": "skeleton-loader.json",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "skeleton",
3
3
  "tier": "free",
4
- "version": "0.4.4",
4
+ "version": "0.4.5",
5
5
  "file": "skeleton.tsx",
6
6
  "sourcePath": "ui/loading/skeleton.tsx",
7
7
  "registryFile": "skeleton.json",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "skew-x-char",
3
3
  "tier": "free",
4
- "version": "0.4.4",
4
+ "version": "0.4.5",
5
5
  "file": "skew-x-char.tsx",
6
6
  "registryFile": "skew-x-char.json",
7
7
  "exports": [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "slide-down-char",
3
3
  "tier": "free",
4
- "version": "0.4.4",
4
+ "version": "0.4.5",
5
5
  "file": "slide-down-char.tsx",
6
6
  "registryFile": "slide-down-char.json",
7
7
  "exports": [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "slide-down-word",
3
3
  "tier": "free",
4
- "version": "0.4.4",
4
+ "version": "0.4.5",
5
5
  "file": "slide-down-word.tsx",
6
6
  "registryFile": "slide-down-word.json",
7
7
  "exports": [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "slide-left-char",
3
3
  "tier": "free",
4
- "version": "0.4.4",
4
+ "version": "0.4.5",
5
5
  "file": "slide-left-char.tsx",
6
6
  "registryFile": "slide-left-char.json",
7
7
  "exports": [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "slide-left",
3
3
  "tier": "free",
4
- "version": "0.4.4",
4
+ "version": "0.4.5",
5
5
  "file": "slide-left.tsx",
6
6
  "sourcePath": "ui/entrance/slide-left.tsx",
7
7
  "registryFile": "slide-left.json",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "slide-right-char",
3
3
  "tier": "free",
4
- "version": "0.4.4",
4
+ "version": "0.4.5",
5
5
  "file": "slide-right-char.tsx",
6
6
  "registryFile": "slide-right-char.json",
7
7
  "exports": [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "slide-right",
3
3
  "tier": "free",
4
- "version": "0.4.4",
4
+ "version": "0.4.5",
5
5
  "file": "slide-right.tsx",
6
6
  "sourcePath": "ui/entrance/slide-right.tsx",
7
7
  "registryFile": "slide-right.json",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "slide-up-char",
3
3
  "tier": "free",
4
- "version": "0.4.4",
4
+ "version": "0.4.5",
5
5
  "file": "slide-up-char.tsx",
6
6
  "registryFile": "slide-up-char.json",
7
7
  "exports": [