ruby_reactor 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +10 -2
  3. data/README.md +72 -3
  4. data/Rakefile +27 -2
  5. data/documentation/images/failed_order_processing.png +0 -0
  6. data/documentation/images/payment_workflow.png +0 -0
  7. data/documentation/interrupts.md +161 -0
  8. data/gui/.gitignore +24 -0
  9. data/gui/README.md +73 -0
  10. data/gui/eslint.config.js +23 -0
  11. data/gui/index.html +13 -0
  12. data/gui/package-lock.json +5925 -0
  13. data/gui/package.json +46 -0
  14. data/gui/postcss.config.js +6 -0
  15. data/gui/public/vite.svg +1 -0
  16. data/gui/src/App.css +42 -0
  17. data/gui/src/App.tsx +51 -0
  18. data/gui/src/assets/react.svg +1 -0
  19. data/gui/src/components/DagVisualizer.tsx +424 -0
  20. data/gui/src/components/Dashboard.tsx +163 -0
  21. data/gui/src/components/ErrorBoundary.tsx +47 -0
  22. data/gui/src/components/ReactorDetail.tsx +135 -0
  23. data/gui/src/components/StepInspector.tsx +492 -0
  24. data/gui/src/components/__tests__/DagVisualizer.test.tsx +140 -0
  25. data/gui/src/components/__tests__/ReactorDetail.test.tsx +111 -0
  26. data/gui/src/components/__tests__/StepInspector.test.tsx +408 -0
  27. data/gui/src/globals.d.ts +7 -0
  28. data/gui/src/index.css +14 -0
  29. data/gui/src/lib/utils.ts +13 -0
  30. data/gui/src/main.tsx +14 -0
  31. data/gui/src/test/setup.ts +11 -0
  32. data/gui/tailwind.config.js +11 -0
  33. data/gui/tsconfig.app.json +28 -0
  34. data/gui/tsconfig.json +7 -0
  35. data/gui/tsconfig.node.json +26 -0
  36. data/gui/vite.config.ts +8 -0
  37. data/gui/vitest.config.ts +13 -0
  38. data/lib/ruby_reactor/async_router.rb +6 -2
  39. data/lib/ruby_reactor/context.rb +35 -9
  40. data/lib/ruby_reactor/dependency_graph.rb +2 -0
  41. data/lib/ruby_reactor/dsl/compose_builder.rb +8 -0
  42. data/lib/ruby_reactor/dsl/interrupt_builder.rb +48 -0
  43. data/lib/ruby_reactor/dsl/interrupt_step_config.rb +21 -0
  44. data/lib/ruby_reactor/dsl/map_builder.rb +8 -0
  45. data/lib/ruby_reactor/dsl/reactor.rb +12 -0
  46. data/lib/ruby_reactor/dsl/step_builder.rb +4 -0
  47. data/lib/ruby_reactor/executor/compensation_manager.rb +60 -27
  48. data/lib/ruby_reactor/executor/graph_manager.rb +2 -0
  49. data/lib/ruby_reactor/executor/result_handler.rb +117 -39
  50. data/lib/ruby_reactor/executor/retry_manager.rb +1 -0
  51. data/lib/ruby_reactor/executor/step_executor.rb +38 -4
  52. data/lib/ruby_reactor/executor.rb +86 -13
  53. data/lib/ruby_reactor/interrupt_result.rb +20 -0
  54. data/lib/ruby_reactor/map/collector.rb +0 -2
  55. data/lib/ruby_reactor/map/element_executor.rb +3 -0
  56. data/lib/ruby_reactor/map/execution.rb +28 -1
  57. data/lib/ruby_reactor/map/helpers.rb +44 -6
  58. data/lib/ruby_reactor/reactor.rb +187 -1
  59. data/lib/ruby_reactor/registry.rb +25 -0
  60. data/lib/ruby_reactor/sidekiq_workers/worker.rb +1 -1
  61. data/lib/ruby_reactor/step/compose_step.rb +22 -6
  62. data/lib/ruby_reactor/step/map_step.rb +30 -3
  63. data/lib/ruby_reactor/storage/adapter.rb +32 -0
  64. data/lib/ruby_reactor/storage/redis_adapter.rb +154 -11
  65. data/lib/ruby_reactor/utils/code_extractor.rb +31 -0
  66. data/lib/ruby_reactor/version.rb +1 -1
  67. data/lib/ruby_reactor/web/api.rb +206 -0
  68. data/lib/ruby_reactor/web/application.rb +53 -0
  69. data/lib/ruby_reactor/web/config.ru +5 -0
  70. data/lib/ruby_reactor/web/public/assets/index-VdeLgH9k.js +19 -0
  71. data/lib/ruby_reactor/web/public/assets/index-_z-6BvuM.css +1 -0
  72. data/lib/ruby_reactor/web/public/index.html +14 -0
  73. data/lib/ruby_reactor/web/public/vite.svg +1 -0
  74. data/lib/ruby_reactor.rb +94 -28
  75. data/llms-full.txt +66 -0
  76. data/llms.txt +7 -0
  77. metadata +63 -2
@@ -0,0 +1 @@
1
+ @import"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap";@layer properties{@supports ((-webkit-hyphens:none) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-space-y-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial}}}@layer theme{:root,:host{--font-sans:"Inter",system-ui,sans-serif;--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-300:oklch(80.8% .114 19.571);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-950:oklch(25.8% .092 26.042);--color-amber-400:oklch(82.8% .189 84.429);--color-amber-500:oklch(76.9% .188 70.08);--color-emerald-400:oklch(76.5% .177 163.223);--color-emerald-500:oklch(69.6% .17 162.48);--color-emerald-600:oklch(59.6% .145 163.225);--color-teal-400:oklch(77.7% .152 181.912);--color-teal-500:oklch(70.4% .14 182.503);--color-indigo-300:oklch(78.5% .115 274.713);--color-indigo-400:oklch(67.3% .182 276.935);--color-indigo-500:#6366f1;--color-indigo-600:oklch(51.1% .262 276.966);--color-rose-400:oklch(71.2% .194 13.428);--color-rose-500:oklch(64.5% .246 16.439);--color-slate-50:oklch(98.4% .003 247.858);--color-slate-200:oklch(92.9% .013 255.508);--color-slate-300:oklch(86.9% .022 252.894);--color-slate-400:oklch(70.4% .04 256.788);--color-slate-500:oklch(55.4% .046 257.417);--color-slate-600:oklch(44.6% .043 257.281);--color-slate-700:oklch(37.2% .044 257.287);--color-slate-800:#1e293b;--color-slate-900:#0f172a;--color-slate-950:#020617;--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--font-weight-medium:500;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-wide:.025em;--tracking-wider:.05em;--tracking-widest:.1em;--leading-tight:1.25;--leading-relaxed:1.625;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite;--blur-sm:8px;--blur-md:12px;--blur-xl:24px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::-moz-placeholder{opacity:1}::placeholder{opacity:1}@supports (not (-webkit-appearance:-apple-pay-button)) or (contain-intrinsic-size:1px){::-moz-placeholder{color:currentColor}::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::-moz-placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){-webkit-appearance:button;-moz-appearance:button;appearance:button}::file-selector-button{-webkit-appearance:button;-moz-appearance:button;appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-none{pointer-events:none}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.relative{position:relative}.static{position:static}.sticky{position:sticky}.top-0{top:calc(var(--spacing)*0)}.top-1\/2{top:50%}.-left-2{left:calc(var(--spacing)*-2)}.left-0{left:calc(var(--spacing)*0)}.left-3{left:calc(var(--spacing)*3)}.isolate{isolation:isolate}.z-50{z-index:50}.mx-auto{margin-inline:auto}.mt-0\.5{margin-top:calc(var(--spacing)*.5)}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-1\.5{margin-top:calc(var(--spacing)*1.5)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-3{margin-top:calc(var(--spacing)*3)}.mb-1\.5{margin-bottom:calc(var(--spacing)*1.5)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.ml-3{margin-left:calc(var(--spacing)*3)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.grid{display:grid}.inline-flex{display:inline-flex}.\!h-2{height:calc(var(--spacing)*2)!important}.h-0{height:calc(var(--spacing)*0)}.h-3{height:calc(var(--spacing)*3)}.h-4{height:calc(var(--spacing)*4)}.h-5{height:calc(var(--spacing)*5)}.h-6{height:calc(var(--spacing)*6)}.h-16{height:calc(var(--spacing)*16)}.h-24{height:calc(var(--spacing)*24)}.h-40{height:calc(var(--spacing)*40)}.h-\[calc\(100vh-8rem\)\]{height:calc(100vh - 8rem)}.h-full{height:100%}.h-px{height:1px}.max-h-\[300px\]{max-height:300px}.min-h-0{min-height:calc(var(--spacing)*0)}.min-h-\[500px\]{min-height:500px}.min-h-screen{min-height:100vh}.\!w-2{width:calc(var(--spacing)*2)!important}.w-1{width:calc(var(--spacing)*1)}.w-3{width:calc(var(--spacing)*3)}.w-4{width:calc(var(--spacing)*4)}.w-5{width:calc(var(--spacing)*5)}.w-6{width:calc(var(--spacing)*6)}.w-full{width:100%}.max-w-7xl{max-width:var(--container-7xl)}.min-w-0{min-width:calc(var(--spacing)*0)}.min-w-\[150px\]{min-width:150px}.flex-1{flex:1}.shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y: -50% ;translate:var(--tw-translate-x)var(--tw-translate-y)}.scale-105{--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x)var(--tw-scale-y)}.animate-pulse{animation:var(--animate-pulse)}.cursor-pointer{cursor:pointer}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-1{gap:calc(var(--spacing)*1)}.gap-1\.5{gap:calc(var(--spacing)*1.5)}.gap-2{gap:calc(var(--spacing)*2)}.gap-3{gap:calc(var(--spacing)*3)}.gap-4{gap:calc(var(--spacing)*4)}.gap-6{gap:calc(var(--spacing)*6)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*2)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*3)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-8>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*8)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*8)*calc(1 - var(--tw-space-y-reverse)))}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-slate-800\/50>:not(:last-child)){border-color:#1e293b80}@supports (color:color-mix(in lab,red,red)){:where(.divide-slate-800\/50>:not(:last-child)){border-color:color-mix(in oklab,var(--color-slate-800)50%,transparent)}}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-xl{border-radius:var(--radius-xl)}.rounded-br-lg{border-bottom-right-radius:var(--radius-lg)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-amber-500\/20{border-color:#f99c0033}@supports (color:color-mix(in lab,red,red)){.border-amber-500\/20{border-color:color-mix(in oklab,var(--color-amber-500)20%,transparent)}}.border-amber-500\/50{border-color:#f99c0080}@supports (color:color-mix(in lab,red,red)){.border-amber-500\/50{border-color:color-mix(in oklab,var(--color-amber-500)50%,transparent)}}.border-emerald-500\/20{border-color:#00bb7f33}@supports (color:color-mix(in lab,red,red)){.border-emerald-500\/20{border-color:color-mix(in oklab,var(--color-emerald-500)20%,transparent)}}.border-indigo-500{border-color:var(--color-indigo-500)}.border-indigo-500\/20{border-color:#6366f133}@supports (color:color-mix(in lab,red,red)){.border-indigo-500\/20{border-color:color-mix(in oklab,var(--color-indigo-500)20%,transparent)}}.border-red-500\/10{border-color:#fb2c361a}@supports (color:color-mix(in lab,red,red)){.border-red-500\/10{border-color:color-mix(in oklab,var(--color-red-500)10%,transparent)}}.border-red-500\/20{border-color:#fb2c3633}@supports (color:color-mix(in lab,red,red)){.border-red-500\/20{border-color:color-mix(in oklab,var(--color-red-500)20%,transparent)}}.border-rose-500{border-color:var(--color-rose-500)}.border-rose-500\/20{border-color:#ff235733}@supports (color:color-mix(in lab,red,red)){.border-rose-500\/20{border-color:color-mix(in oklab,var(--color-rose-500)20%,transparent)}}.border-slate-700{border-color:var(--color-slate-700)}.border-slate-800{border-color:var(--color-slate-800)}.border-slate-800\/50{border-color:#1e293b80}@supports (color:color-mix(in lab,red,red)){.border-slate-800\/50{border-color:color-mix(in oklab,var(--color-slate-800)50%,transparent)}}.border-slate-800\/60{border-color:#1e293b99}@supports (color:color-mix(in lab,red,red)){.border-slate-800\/60{border-color:color-mix(in oklab,var(--color-slate-800)60%,transparent)}}.border-teal-500{border-color:var(--color-teal-500)}.border-teal-500\/20{border-color:#00baa733}@supports (color:color-mix(in lab,red,red)){.border-teal-500\/20{border-color:color-mix(in oklab,var(--color-teal-500)20%,transparent)}}.border-white\/5{border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.border-white\/5{border-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.\!bg-slate-500{background-color:var(--color-slate-500)!important}.bg-amber-500\/10{background-color:#f99c001a}@supports (color:color-mix(in lab,red,red)){.bg-amber-500\/10{background-color:color-mix(in oklab,var(--color-amber-500)10%,transparent)}}.bg-black\/30{background-color:#0000004d}@supports (color:color-mix(in lab,red,red)){.bg-black\/30{background-color:color-mix(in oklab,var(--color-black)30%,transparent)}}.bg-emerald-500\/5{background-color:#00bb7f0d}@supports (color:color-mix(in lab,red,red)){.bg-emerald-500\/5{background-color:color-mix(in oklab,var(--color-emerald-500)5%,transparent)}}.bg-emerald-500\/10{background-color:#00bb7f1a}@supports (color:color-mix(in lab,red,red)){.bg-emerald-500\/10{background-color:color-mix(in oklab,var(--color-emerald-500)10%,transparent)}}.bg-emerald-600{background-color:var(--color-emerald-600)}.bg-indigo-500{background-color:var(--color-indigo-500)}.bg-indigo-500\/10{background-color:#6366f11a}@supports (color:color-mix(in lab,red,red)){.bg-indigo-500\/10{background-color:color-mix(in oklab,var(--color-indigo-500)10%,transparent)}}.bg-indigo-600{background-color:var(--color-indigo-600)}.bg-red-500\/10{background-color:#fb2c361a}@supports (color:color-mix(in lab,red,red)){.bg-red-500\/10{background-color:color-mix(in oklab,var(--color-red-500)10%,transparent)}}.bg-red-950\/20{background-color:#46080933}@supports (color:color-mix(in lab,red,red)){.bg-red-950\/20{background-color:color-mix(in oklab,var(--color-red-950)20%,transparent)}}.bg-rose-500\/10{background-color:#ff23571a}@supports (color:color-mix(in lab,red,red)){.bg-rose-500\/10{background-color:color-mix(in oklab,var(--color-rose-500)10%,transparent)}}.bg-slate-700\/50{background-color:#31415880}@supports (color:color-mix(in lab,red,red)){.bg-slate-700\/50{background-color:color-mix(in oklab,var(--color-slate-700)50%,transparent)}}.bg-slate-800{background-color:var(--color-slate-800)}.bg-slate-900{background-color:var(--color-slate-900)}.bg-slate-900\/30{background-color:#0f172a4d}@supports (color:color-mix(in lab,red,red)){.bg-slate-900\/30{background-color:color-mix(in oklab,var(--color-slate-900)30%,transparent)}}.bg-slate-900\/50{background-color:#0f172a80}@supports (color:color-mix(in lab,red,red)){.bg-slate-900\/50{background-color:color-mix(in oklab,var(--color-slate-900)50%,transparent)}}.bg-slate-900\/80{background-color:#0f172acc}@supports (color:color-mix(in lab,red,red)){.bg-slate-900\/80{background-color:color-mix(in oklab,var(--color-slate-900)80%,transparent)}}.bg-slate-950{background-color:var(--color-slate-950)}.bg-slate-950\/50{background-color:#02061780}@supports (color:color-mix(in lab,red,red)){.bg-slate-950\/50{background-color:color-mix(in oklab,var(--color-slate-950)50%,transparent)}}.bg-teal-500\/10{background-color:#00baa71a}@supports (color:color-mix(in lab,red,red)){.bg-teal-500\/10{background-color:color-mix(in oklab,var(--color-teal-500)10%,transparent)}}.fill-slate-400{fill:var(--color-slate-400)}.p-1{padding:calc(var(--spacing)*1)}.p-1\.5{padding:calc(var(--spacing)*1.5)}.p-2{padding:calc(var(--spacing)*2)}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.p-6{padding:calc(var(--spacing)*6)}.p-8{padding:calc(var(--spacing)*8)}.p-12{padding:calc(var(--spacing)*12)}.px-1{padding-inline:calc(var(--spacing)*1)}.px-1\.5{padding-inline:calc(var(--spacing)*1.5)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-2\.5{padding-inline:calc(var(--spacing)*2.5)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-6{padding-inline:calc(var(--spacing)*6)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-2{padding-block:calc(var(--spacing)*2)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.py-8{padding-block:calc(var(--spacing)*8)}.py-24{padding-block:calc(var(--spacing)*24)}.pt-3{padding-top:calc(var(--spacing)*3)}.pr-4{padding-right:calc(var(--spacing)*4)}.pr-8{padding-right:calc(var(--spacing)*8)}.pb-4{padding-bottom:calc(var(--spacing)*4)}.pb-6{padding-bottom:calc(var(--spacing)*6)}.pl-2{padding-left:calc(var(--spacing)*2)}.pl-3{padding-left:calc(var(--spacing)*3)}.pl-9{padding-left:calc(var(--spacing)*9)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.font-mono{font-family:var(--font-mono)}.font-sans{font-family:var(--font-sans)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.whitespace-pre-wrap{white-space:pre-wrap}.text-amber-400{color:var(--color-amber-400)}.text-amber-500{color:var(--color-amber-500)}.text-emerald-400{color:var(--color-emerald-400)}.text-indigo-400{color:var(--color-indigo-400)}.text-red-300{color:var(--color-red-300)}.text-red-300\/90{color:#ffa3a3e6}@supports (color:color-mix(in lab,red,red)){.text-red-300\/90{color:color-mix(in oklab,var(--color-red-300)90%,transparent)}}.text-red-400{color:var(--color-red-400)}.text-red-400\/30{color:#ff65684d}@supports (color:color-mix(in lab,red,red)){.text-red-400\/30{color:color-mix(in oklab,var(--color-red-400)30%,transparent)}}.text-red-400\/50{color:#ff656880}@supports (color:color-mix(in lab,red,red)){.text-red-400\/50{color:color-mix(in oklab,var(--color-red-400)50%,transparent)}}.text-red-400\/70{color:#ff6568b3}@supports (color:color-mix(in lab,red,red)){.text-red-400\/70{color:color-mix(in oklab,var(--color-red-400)70%,transparent)}}.text-red-400\/80{color:#ff6568cc}@supports (color:color-mix(in lab,red,red)){.text-red-400\/80{color:color-mix(in oklab,var(--color-red-400)80%,transparent)}}.text-red-500{color:var(--color-red-500)}.text-rose-400{color:var(--color-rose-400)}.text-slate-50{color:var(--color-slate-50)}.text-slate-200{color:var(--color-slate-200)}.text-slate-300{color:var(--color-slate-300)}.text-slate-400{color:var(--color-slate-400)}.text-slate-500{color:var(--color-slate-500)}.text-slate-600{color:var(--color-slate-600)}.text-teal-400{color:var(--color-teal-400)}.text-white{color:var(--color-white)}.capitalize{text-transform:capitalize}.uppercase{text-transform:uppercase}.italic{font-style:italic}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,)var(--tw-slashed-zero,)var(--tw-numeric-figure,)var(--tw-numeric-spacing,)var(--tw-numeric-fraction,)}.placeholder-slate-700::-moz-placeholder{color:var(--color-slate-700)}.placeholder-slate-700::placeholder{color:var(--color-slate-700)}.opacity-0{opacity:0}.opacity-50{opacity:.5}.opacity-70{opacity:.7}.opacity-75{opacity:.75}.shadow-\[0_0_10px_rgba\(20\,184\,166\,0\.15\)\]{--tw-shadow:0 0 10px var(--tw-shadow-color,#14b8a626);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[0_0_10px_rgba\(99\,102\,241\,0\.15\)\]{--tw-shadow:0 0 10px var(--tw-shadow-color,#6366f126);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[0_0_10px_rgba\(244\,63\,94\,0\.15\)\]{--tw-shadow:0 0 10px var(--tw-shadow-color,#f43f5e26);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[0_0_15px_rgba\(244\,63\,94\,0\.2\)\]{--tw-shadow:0 0 15px var(--tw-shadow-color,#f43f5e33);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-2{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-black\/20{--tw-shadow-color:#0003}@supports (color:color-mix(in lab,red,red)){.shadow-black\/20{--tw-shadow-color:color-mix(in oklab,color-mix(in oklab,var(--color-black)20%,transparent)var(--tw-shadow-alpha),transparent)}}.shadow-emerald-500\/20{--tw-shadow-color:#00bb7f33}@supports (color:color-mix(in lab,red,red)){.shadow-emerald-500\/20{--tw-shadow-color:color-mix(in oklab,color-mix(in oklab,var(--color-emerald-500)20%,transparent)var(--tw-shadow-alpha),transparent)}}.shadow-indigo-500\/20{--tw-shadow-color:#6366f133}@supports (color:color-mix(in lab,red,red)){.shadow-indigo-500\/20{--tw-shadow-color:color-mix(in oklab,color-mix(in oklab,var(--color-indigo-500)20%,transparent)var(--tw-shadow-alpha),transparent)}}.ring-indigo-500\/20{--tw-ring-color:#6366f133}@supports (color:color-mix(in lab,red,red)){.ring-indigo-500\/20{--tw-ring-color:color-mix(in oklab,var(--color-indigo-500)20%,transparent)}}.ring-white\/20{--tw-ring-color:#fff3}@supports (color:color-mix(in lab,red,red)){.ring-white\/20{--tw-ring-color:color-mix(in oklab,var(--color-white)20%,transparent)}}.backdrop-blur-md{--tw-backdrop-blur:blur(var(--blur-md));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.backdrop-blur-sm{--tw-backdrop-blur:blur(var(--blur-sm));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.backdrop-blur-xl{--tw-backdrop-blur:blur(var(--blur-xl));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-300{--tw-duration:.3s;transition-duration:.3s}@media(hover:hover){.group-hover\:h-4:is(:where(.group):hover *){height:calc(var(--spacing)*4)}.group-hover\:bg-indigo-500\/20:is(:where(.group):hover *){background-color:#6366f133}@supports (color:color-mix(in lab,red,red)){.group-hover\:bg-indigo-500\/20:is(:where(.group):hover *){background-color:color-mix(in oklab,var(--color-indigo-500)20%,transparent)}}.group-hover\:text-indigo-300:is(:where(.group):hover *){color:var(--color-indigo-300)}.group-hover\:text-indigo-400:is(:where(.group):hover *){color:var(--color-indigo-400)}.group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}}.selection\:bg-indigo-500\/30 ::-moz-selection{background-color:#6366f14d}.selection\:bg-indigo-500\/30 ::selection{background-color:#6366f14d}@supports (color:color-mix(in lab,red,red)){.selection\:bg-indigo-500\/30 ::-moz-selection{background-color:color-mix(in oklab,var(--color-indigo-500)30%,transparent)}.selection\:bg-indigo-500\/30 ::selection{background-color:color-mix(in oklab,var(--color-indigo-500)30%,transparent)}}.selection\:bg-indigo-500\/30::-moz-selection{background-color:#6366f14d}.selection\:bg-indigo-500\/30::selection{background-color:#6366f14d}@supports (color:color-mix(in lab,red,red)){.selection\:bg-indigo-500\/30::-moz-selection{background-color:color-mix(in oklab,var(--color-indigo-500)30%,transparent)}.selection\:bg-indigo-500\/30::selection{background-color:color-mix(in oklab,var(--color-indigo-500)30%,transparent)}}.placeholder\:text-slate-600::-moz-placeholder{color:var(--color-slate-600)}.placeholder\:text-slate-600::placeholder{color:var(--color-slate-600)}@media(hover:hover){.hover\:-translate-y-0\.5:hover{--tw-translate-y:calc(var(--spacing)*-.5);translate:var(--tw-translate-x)var(--tw-translate-y)}.hover\:bg-emerald-500:hover{background-color:var(--color-emerald-500)}.hover\:bg-indigo-500:hover{background-color:var(--color-indigo-500)}.hover\:bg-indigo-500\/10:hover{background-color:#6366f11a}@supports (color:color-mix(in lab,red,red)){.hover\:bg-indigo-500\/10:hover{background-color:color-mix(in oklab,var(--color-indigo-500)10%,transparent)}}.hover\:bg-slate-800:hover{background-color:var(--color-slate-800)}.hover\:bg-slate-800\/30:hover{background-color:#1e293b4d}@supports (color:color-mix(in lab,red,red)){.hover\:bg-slate-800\/30:hover{background-color:color-mix(in oklab,var(--color-slate-800)30%,transparent)}}.hover\:bg-white\/5:hover{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.hover\:bg-white\/5:hover{background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.hover\:bg-white\/10:hover{background-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.hover\:bg-white\/10:hover{background-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.hover\:text-indigo-400:hover{color:var(--color-indigo-400)}.hover\:text-red-400:hover{color:var(--color-red-400)}.hover\:text-white:hover{color:var(--color-white)}}.focus\:border-emerald-500\/50:focus{border-color:#00bb7f80}@supports (color:color-mix(in lab,red,red)){.focus\:border-emerald-500\/50:focus{border-color:color-mix(in oklab,var(--color-emerald-500)50%,transparent)}}.focus\:ring-1:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-emerald-500\/50:focus{--tw-ring-color:#00bb7f80}@supports (color:color-mix(in lab,red,red)){.focus\:ring-emerald-500\/50:focus{--tw-ring-color:color-mix(in oklab,var(--color-emerald-500)50%,transparent)}}.focus\:ring-indigo-500\/50:focus{--tw-ring-color:#6366f180}@supports (color:color-mix(in lab,red,red)){.focus\:ring-indigo-500\/50:focus{--tw-ring-color:color-mix(in oklab,var(--color-indigo-500)50%,transparent)}}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}@media(min-width:40rem){.sm\:w-64{width:calc(var(--spacing)*64)}.sm\:flex-row{flex-direction:row}.sm\:items-center{align-items:center}.sm\:px-6{padding-inline:calc(var(--spacing)*6)}}@media(min-width:64rem){.lg\:col-span-2{grid-column:span 2/span 2}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:px-8{padding-inline:calc(var(--spacing)*8)}}}body{background-color:var(--color-slate-950);font-family:var(--font-sans);color:var(--color-slate-50);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@keyframes pulse{50%{opacity:.5}}.react-flow{direction:ltr;--xy-edge-stroke-default: #b1b1b7;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #555;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(255, 255, 255, .5);--xy-minimap-background-color-default: #fff;--xy-minimap-mask-background-color-default: rgba(240, 240, 240, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #e2e2e2;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: transparent;--xy-background-pattern-dots-color-default: #91919a;--xy-background-pattern-lines-color-default: #eee;--xy-background-pattern-cross-color-default: #e2e2e2;background-color:var(--xy-background-color, var(--xy-background-color-default));--xy-node-color-default: inherit;--xy-node-border-default: 1px solid #1a192b;--xy-node-background-color-default: #fff;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(0, 0, 0, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #1a192b;--xy-node-border-radius-default: 3px;--xy-handle-background-color-default: #1a192b;--xy-handle-border-color-default: #fff;--xy-selection-background-color-default: rgba(0, 89, 220, .08);--xy-selection-border-default: 1px dotted rgba(0, 89, 220, .8);--xy-controls-button-background-color-default: #fefefe;--xy-controls-button-background-color-hover-default: #f4f4f4;--xy-controls-button-color-default: inherit;--xy-controls-button-color-hover-default: inherit;--xy-controls-button-border-color-default: #eee;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #ffffff;--xy-edge-label-color-default: inherit;--xy-resize-background-color-default: #3367d9}.react-flow.dark{--xy-edge-stroke-default: #3e3e3e;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #727272;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(150, 150, 150, .25);--xy-minimap-background-color-default: #141414;--xy-minimap-mask-background-color-default: rgba(60, 60, 60, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #2b2b2b;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: #141414;--xy-background-pattern-dots-color-default: #777;--xy-background-pattern-lines-color-default: #777;--xy-background-pattern-cross-color-default: #777;--xy-node-color-default: #f8f8f8;--xy-node-border-default: 1px solid #3c3c3c;--xy-node-background-color-default: #1e1e1e;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(255, 255, 255, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #999;--xy-handle-background-color-default: #bebebe;--xy-handle-border-color-default: #1e1e1e;--xy-selection-background-color-default: rgba(200, 200, 220, .08);--xy-selection-border-default: 1px dotted rgba(200, 200, 220, .8);--xy-controls-button-background-color-default: #2b2b2b;--xy-controls-button-background-color-hover-default: #3e3e3e;--xy-controls-button-color-default: #f8f8f8;--xy-controls-button-color-hover-default: #fff;--xy-controls-button-border-color-default: #5b5b5b;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #141414;--xy-edge-label-color-default: #f8f8f8}.react-flow__background{background-color:var(--xy-background-color-props, var(--xy-background-color, var(--xy-background-color-default)));pointer-events:none;z-index:-1}.react-flow__container{position:absolute;width:100%;height:100%;top:0;left:0}.react-flow__pane{z-index:1}.react-flow__pane.draggable{cursor:grab}.react-flow__pane.dragging{cursor:grabbing}.react-flow__pane.selection{cursor:pointer}.react-flow__viewport{transform-origin:0 0;z-index:2;pointer-events:none}.react-flow__renderer{z-index:4}.react-flow__selection{z-index:6}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible{outline:none}.react-flow__edge-path{stroke:var(--xy-edge-stroke, var(--xy-edge-stroke-default));stroke-width:var(--xy-edge-stroke-width, var(--xy-edge-stroke-width-default));fill:none}.react-flow__connection-path{stroke:var(--xy-connectionline-stroke, var(--xy-connectionline-stroke-default));stroke-width:var(--xy-connectionline-stroke-width, var(--xy-connectionline-stroke-width-default));fill:none}.react-flow .react-flow__edges{position:absolute}.react-flow .react-flow__edges svg{overflow:visible;position:absolute;pointer-events:none}.react-flow__edge{pointer-events:visibleStroke}.react-flow__edge.selectable{cursor:pointer}.react-flow__edge.animated path{stroke-dasharray:5;animation:dashdraw .5s linear infinite}.react-flow__edge.animated path.react-flow__edge-interaction{stroke-dasharray:none;animation:none}.react-flow__edge.inactive{pointer-events:none}.react-flow__edge.selected,.react-flow__edge:focus,.react-flow__edge:focus-visible{outline:none}.react-flow__edge.selected .react-flow__edge-path,.react-flow__edge.selectable:focus .react-flow__edge-path,.react-flow__edge.selectable:focus-visible .react-flow__edge-path{stroke:var(--xy-edge-stroke-selected, var(--xy-edge-stroke-selected-default))}.react-flow__edge-textwrapper{pointer-events:all}.react-flow__edge .react-flow__edge-text{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__arrowhead polyline{stroke:var(--xy-edge-stroke, var(--xy-edge-stroke-default))}.react-flow__arrowhead polyline.arrowclosed{fill:var(--xy-edge-stroke, var(--xy-edge-stroke-default))}.react-flow__connection{pointer-events:none}.react-flow__connection .animated{stroke-dasharray:5;animation:dashdraw .5s linear infinite}svg.react-flow__connectionline{z-index:1001;overflow:visible;position:absolute}.react-flow__nodes{pointer-events:none;transform-origin:0 0}.react-flow__node{position:absolute;-webkit-user-select:none;-moz-user-select:none;user-select:none;pointer-events:all;transform-origin:0 0;box-sizing:border-box;cursor:default}.react-flow__node.selectable{cursor:pointer}.react-flow__node.draggable{cursor:grab;pointer-events:all}.react-flow__node.draggable.dragging{cursor:grabbing}.react-flow__nodesselection{z-index:3;transform-origin:left top;pointer-events:none}.react-flow__nodesselection-rect{position:absolute;pointer-events:all;cursor:grab}.react-flow__handle{position:absolute;pointer-events:none;min-width:5px;min-height:5px;width:6px;height:6px;background-color:var(--xy-handle-background-color, var(--xy-handle-background-color-default));border:1px solid var(--xy-handle-border-color, var(--xy-handle-border-color-default));border-radius:100%}.react-flow__handle.connectingfrom{pointer-events:all}.react-flow__handle.connectionindicator{pointer-events:all;cursor:crosshair}.react-flow__handle-bottom{top:auto;left:50%;bottom:0;transform:translate(-50%,50%)}.react-flow__handle-top{top:0;left:50%;transform:translate(-50%,-50%)}.react-flow__handle-left{top:50%;left:0;transform:translate(-50%,-50%)}.react-flow__handle-right{top:50%;right:0;transform:translate(50%,-50%)}.react-flow__edgeupdater{cursor:move;pointer-events:all}.react-flow__pane.selection .react-flow__panel{pointer-events:none}.react-flow__panel{position:absolute;z-index:5;margin:15px}.react-flow__panel.top{top:0}.react-flow__panel.bottom{bottom:0}.react-flow__panel.top.center,.react-flow__panel.bottom.center{left:50%;transform:translate(-15px) translate(-50%)}.react-flow__panel.left{left:0}.react-flow__panel.right{right:0}.react-flow__panel.left.center,.react-flow__panel.right.center{top:50%;transform:translateY(-15px) translateY(-50%)}.react-flow__attribution{font-size:10px;background:var(--xy-attribution-background-color, var(--xy-attribution-background-color-default));padding:2px 3px;margin:0}.react-flow__attribution a{text-decoration:none;color:#999}@keyframes dashdraw{0%{stroke-dashoffset:10}}.react-flow__edgelabel-renderer{position:absolute;width:100%;height:100%;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;left:0;top:0}.react-flow__viewport-portal{position:absolute;width:100%;height:100%;left:0;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__minimap{background:var( --xy-minimap-background-color-props, var(--xy-minimap-background-color, var(--xy-minimap-background-color-default)) )}.react-flow__minimap-svg{display:block}.react-flow__minimap-mask{fill:var( --xy-minimap-mask-background-color-props, var(--xy-minimap-mask-background-color, var(--xy-minimap-mask-background-color-default)) );stroke:var( --xy-minimap-mask-stroke-color-props, var(--xy-minimap-mask-stroke-color, var(--xy-minimap-mask-stroke-color-default)) );stroke-width:var( --xy-minimap-mask-stroke-width-props, var(--xy-minimap-mask-stroke-width, var(--xy-minimap-mask-stroke-width-default)) )}.react-flow__minimap-node{fill:var( --xy-minimap-node-background-color-props, var(--xy-minimap-node-background-color, var(--xy-minimap-node-background-color-default)) );stroke:var( --xy-minimap-node-stroke-color-props, var(--xy-minimap-node-stroke-color, var(--xy-minimap-node-stroke-color-default)) );stroke-width:var( --xy-minimap-node-stroke-width-props, var(--xy-minimap-node-stroke-width, var(--xy-minimap-node-stroke-width-default)) )}.react-flow__background-pattern.dots{fill:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-dots-color-default)) )}.react-flow__background-pattern.lines{stroke:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-lines-color-default)) )}.react-flow__background-pattern.cross{stroke:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-cross-color-default)) )}.react-flow__controls{display:flex;flex-direction:column;box-shadow:var(--xy-controls-box-shadow, var(--xy-controls-box-shadow-default))}.react-flow__controls.horizontal{flex-direction:row}.react-flow__controls-button{display:flex;justify-content:center;align-items:center;height:26px;width:26px;padding:4px;border:none;background:var(--xy-controls-button-background-color, var(--xy-controls-button-background-color-default));border-bottom:1px solid var( --xy-controls-button-border-color-props, var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default)) );color:var( --xy-controls-button-color-props, var(--xy-controls-button-color, var(--xy-controls-button-color-default)) );cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__controls-button svg{width:100%;max-width:12px;max-height:12px;fill:currentColor}.react-flow__edge.updating .react-flow__edge-path{stroke:#777}.react-flow__edge-text{font-size:10px}.react-flow__node.selectable:focus,.react-flow__node.selectable:focus-visible{outline:none}.react-flow__node-input,.react-flow__node-default,.react-flow__node-output,.react-flow__node-group{padding:10px;border-radius:var(--xy-node-border-radius, var(--xy-node-border-radius-default));width:150px;font-size:12px;color:var(--xy-node-color, var(--xy-node-color-default));text-align:center;border:var(--xy-node-border, var(--xy-node-border-default));background-color:var(--xy-node-background-color, var(--xy-node-background-color-default))}.react-flow__node-input.selectable:hover,.react-flow__node-default.selectable:hover,.react-flow__node-output.selectable:hover,.react-flow__node-group.selectable:hover{box-shadow:var(--xy-node-boxshadow-hover, var(--xy-node-boxshadow-hover-default))}.react-flow__node-input.selectable.selected,.react-flow__node-input.selectable:focus,.react-flow__node-input.selectable:focus-visible,.react-flow__node-default.selectable.selected,.react-flow__node-default.selectable:focus,.react-flow__node-default.selectable:focus-visible,.react-flow__node-output.selectable.selected,.react-flow__node-output.selectable:focus,.react-flow__node-output.selectable:focus-visible,.react-flow__node-group.selectable.selected,.react-flow__node-group.selectable:focus,.react-flow__node-group.selectable:focus-visible{box-shadow:var(--xy-node-boxshadow-selected, var(--xy-node-boxshadow-selected-default))}.react-flow__node-group{background-color:var(--xy-node-group-background-color, var(--xy-node-group-background-color-default))}.react-flow__nodesselection-rect,.react-flow__selection{background:var(--xy-selection-background-color, var(--xy-selection-background-color-default));border:var(--xy-selection-border, var(--xy-selection-border-default))}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible,.react-flow__selection:focus,.react-flow__selection:focus-visible{outline:none}.react-flow__controls-button:hover{background:var( --xy-controls-button-background-color-hover-props, var(--xy-controls-button-background-color-hover, var(--xy-controls-button-background-color-hover-default)) );color:var( --xy-controls-button-color-hover-props, var(--xy-controls-button-color-hover, var(--xy-controls-button-color-hover-default)) )}.react-flow__controls-button:disabled{pointer-events:none}.react-flow__controls-button:disabled svg{fill-opacity:.4}.react-flow__controls-button:last-child{border-bottom:none}.react-flow__controls.horizontal .react-flow__controls-button{border-bottom:none;border-right:1px solid var( --xy-controls-button-border-color-props, var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default)) )}.react-flow__controls.horizontal .react-flow__controls-button:last-child{border-right:none}.react-flow__resize-control{position:absolute}.react-flow__resize-control.left,.react-flow__resize-control.right{cursor:ew-resize}.react-flow__resize-control.top,.react-flow__resize-control.bottom{cursor:ns-resize}.react-flow__resize-control.top.left,.react-flow__resize-control.bottom.right{cursor:nwse-resize}.react-flow__resize-control.bottom.left,.react-flow__resize-control.top.right{cursor:nesw-resize}.react-flow__resize-control.handle{width:5px;height:5px;border:1px solid #fff;border-radius:1px;background-color:var(--xy-resize-background-color, var(--xy-resize-background-color-default));translate:-50% -50%}.react-flow__resize-control.handle.left{left:0;top:50%}.react-flow__resize-control.handle.right{left:100%;top:50%}.react-flow__resize-control.handle.top{left:50%;top:0}.react-flow__resize-control.handle.bottom{left:50%;top:100%}.react-flow__resize-control.handle.top.left,.react-flow__resize-control.handle.bottom.left{left:0}.react-flow__resize-control.handle.top.right,.react-flow__resize-control.handle.bottom.right{left:100%}.react-flow__resize-control.line{border-color:var(--xy-resize-background-color, var(--xy-resize-background-color-default));border-width:0;border-style:solid}.react-flow__resize-control.line.left,.react-flow__resize-control.line.right{width:1px;transform:translate(-50%);top:0;height:100%}.react-flow__resize-control.line.left{left:0;border-left-width:1px}.react-flow__resize-control.line.right{left:100%;border-right-width:1px}.react-flow__resize-control.line.top,.react-flow__resize-control.line.bottom{height:1px;transform:translateY(-50%);left:0;width:100%}.react-flow__resize-control.line.top{top:0;border-top-width:1px}.react-flow__resize-control.line.bottom{border-bottom-width:1px;top:100%}.react-flow__edge-textbg{fill:var(--xy-edge-label-background-color, var(--xy-edge-label-background-color-default))}.react-flow__edge-text{fill:var(--xy-edge-label-color, var(--xy-edge-label-color-default))}
@@ -0,0 +1,14 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="./vite.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>ui</title>
8
+ <script type="module" crossorigin src="./assets/index-VdeLgH9k.js"></script>
9
+ <link rel="stylesheet" crossorigin href="./assets/index-_z-6BvuM.css">
10
+ </head>
11
+ <body>
12
+ <div id="root"></div>
13
+ </body>
14
+ </html>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
data/lib/ruby_reactor.rb CHANGED
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "zeitwerk"
4
+ require "pathname"
5
+ require_relative "ruby_reactor/registry"
6
+ require_relative "ruby_reactor/utils/code_extractor"
4
7
 
5
8
  # Load dry-validation if available (for validation features)
6
9
  begin
@@ -17,6 +20,7 @@ rescue LoadError
17
20
  end
18
21
 
19
22
  loader = Zeitwerk::Loader.for_gem
23
+ loader.inflector.inflect("api" => "API")
20
24
  loader.setup
21
25
 
22
26
  module RubyReactor
@@ -38,11 +42,13 @@ module RubyReactor
38
42
  end
39
43
 
40
44
  class Failure
41
- attr_reader :error, :retryable, :step_name, :inputs, :backtrace, :reactor_name, :step_arguments
45
+ attr_reader :error, :retryable, :step_name, :inputs, :backtrace, :reactor_name, :step_arguments, :exception_class,
46
+ :file_path, :line_number, :code_snippet, :validation_errors
42
47
 
43
48
  # rubocop:disable Metrics/ParameterLists
44
49
  def initialize(error, retryable: nil, step_name: nil, inputs: {}, backtrace: nil, redact_inputs: [],
45
- reactor_name: nil, step_arguments: {})
50
+ reactor_name: nil, step_arguments: {}, exception_class: nil,
51
+ file_path: nil, line_number: nil, code_snippet: nil, invalid_payload: false, validation_errors: nil)
46
52
  # rubocop:enable Metrics/ParameterLists
47
53
  @error = error
48
54
  @retryable = if retryable.nil?
@@ -54,8 +60,15 @@ module RubyReactor
54
60
  @reactor_name = reactor_name
55
61
  @inputs = inputs
56
62
  @step_arguments = step_arguments
57
- @backtrace = backtrace || (error.respond_to?(:backtrace) ? error.backtrace : caller)
63
+ raw_backtrace = backtrace || (error.respond_to?(:backtrace) ? error.backtrace : caller)
64
+ @backtrace = filter_backtrace(raw_backtrace)
58
65
  @redact_inputs = redact_inputs
66
+ @exception_class = exception_class || (error.is_a?(Exception) ? error.class.name : nil)
67
+ @file_path = file_path
68
+ @line_number = line_number
69
+ @code_snippet = code_snippet
70
+ @invalid_payload = invalid_payload
71
+ @validation_errors = validation_errors
59
72
  end
60
73
 
61
74
  def success?
@@ -70,48 +83,96 @@ module RubyReactor
70
83
  @retryable
71
84
  end
72
85
 
86
+ def invalid_payload?
87
+ @invalid_payload
88
+ end
89
+
73
90
  def message
74
- msg = []
91
+ msg = [build_header]
92
+ msg << "Location: #{file_path}:#{line_number}" if file_path && line_number
93
+
94
+ append_code_snippet(msg)
95
+ append_inputs(msg)
96
+ append_step_arguments(msg)
97
+ append_backtrace(msg)
98
+
99
+ msg.join("\n")
100
+ end
101
+
102
+ private
103
+
104
+ def build_header
75
105
  header = "Error"
76
106
  header += " in reactor '#{reactor_name}'" if reactor_name
77
107
  header += " step '#{step_name}'" if step_name
78
108
  header += ": #{error_message}"
109
+ header
110
+ end
79
111
 
80
- msg << header
112
+ def append_code_snippet(msg)
113
+ return unless code_snippet && !code_snippet.empty?
81
114
 
82
- if inputs && !inputs.empty?
83
- msg << "Inputs:"
84
- inputs.each do |key, value|
85
- val = @redact_inputs.include?(key) ? "[REDACTED]" : value.inspect
86
- msg << " #{key}: #{val}"
87
- end
115
+ msg << "Code Snippet:"
116
+ msg << "```"
117
+ code_snippet.each do |line|
118
+ prefix = line[:target] ? ">" : " "
119
+ msg << "#{prefix} #{line[:line_number].to_s.rjust(4)} #{line[:content]}"
88
120
  end
121
+ msg << "```"
122
+ end
89
123
 
90
- if step_arguments && !step_arguments.empty?
91
- msg << "Step Arguments:"
92
- step_arguments.each do |key, value|
93
- # We might want to redact step arguments too if they come from redacted inputs
94
- # For now, let's assume if the input key matches a redacted input key, it should be redacted
95
- # But step arguments have different names.
96
- # We can't easily track redaction for step arguments without more metadata.
97
- # For now, let's just display them.
98
- msg << " #{key}: #{value.inspect}"
99
- end
124
+ def append_inputs(msg)
125
+ return unless inputs && !inputs.empty?
126
+
127
+ msg << "Inputs:"
128
+ inputs.each do |key, value|
129
+ val = @redact_inputs.include?(key) ? "[REDACTED]" : value.inspect
130
+ msg << " #{key}: #{val}"
100
131
  end
132
+ end
133
+
134
+ def append_step_arguments(msg)
135
+ return unless step_arguments && !step_arguments.empty?
101
136
 
102
- if backtrace
103
- msg << "Backtrace:"
104
- msg << backtrace.take(5).map { |line| " #{line}" }.join("\n")
137
+ msg << "Step Arguments:"
138
+ step_arguments.each do |key, value|
139
+ msg << " #{key}: #{value.inspect}"
105
140
  end
141
+ end
106
142
 
107
- msg.join("\n")
143
+ def append_backtrace(msg)
144
+ return unless backtrace
145
+
146
+ msg << "Backtrace:"
147
+ msg << backtrace.take(10).map { |line| " #{line}" }.join("\n")
108
148
  end
109
149
 
110
150
  def to_s
111
151
  message
112
152
  end
113
153
 
114
- private
154
+ def filter_backtrace(backtrace)
155
+ return backtrace if ENV["RUBY_REACTOR_DEBUG"] == "true"
156
+ return backtrace if backtrace.nil? || backtrace.empty?
157
+
158
+ root_path = RubyReactor.root.to_s
159
+ filtered = []
160
+ filtered << backtrace.first
161
+
162
+ internal_block = false
163
+ backtrace[1..]&.each do |line|
164
+ if line.start_with?(root_path)
165
+ unless internal_block
166
+ filtered << "... [ruby-reactor-internals-redacted-trace]"
167
+ internal_block = true
168
+ end
169
+ else
170
+ filtered << line
171
+ internal_block = false
172
+ end
173
+ end
174
+ filtered
175
+ end
115
176
 
116
177
  def error_message
117
178
  @error.respond_to?(:message) ? @error.message : @error.to_s
@@ -120,11 +181,12 @@ module RubyReactor
120
181
 
121
182
  # Async result for background job execution
122
183
  class AsyncResult
123
- attr_reader :job_id, :intermediate_results
184
+ attr_reader :job_id, :intermediate_results, :execution_id
124
185
 
125
- def initialize(job_id:, intermediate_results: {})
186
+ def initialize(job_id:, intermediate_results: {}, execution_id: nil)
126
187
  @job_id = job_id
127
188
  @intermediate_results = intermediate_results
189
+ @execution_id = execution_id
128
190
  end
129
191
 
130
192
  def async?
@@ -156,4 +218,8 @@ module RubyReactor
156
218
  def self.configuration
157
219
  Configuration.instance
158
220
  end
221
+
222
+ def self.root
223
+ Pathname.new(File.expand_path("..", __dir__))
224
+ end
159
225
  end
data/llms-full.txt ADDED
@@ -0,0 +1,66 @@
1
+ # Ruby Reactor: Full LLM Guide
2
+
3
+ This document provides a comprehensive reference for AI agents working on the Ruby Reactor project. It covers commands for the core gem, the demo application, and the GUI, along with debugging and verification workflows.
4
+
5
+ ## Project Structure
6
+
7
+ - `lib/`: Core gem logic.
8
+ - `demo_app/`: A Rails application showcasing Ruby Reactor features.
9
+ - `gui/`: A React-based interface for visualizing reactor execution.
10
+ - `documentation/`: Detailed guides on core concepts, DAG, async, etc.
11
+ - `spec/`: RSpec tests for the gem.
12
+
13
+ ## Core Gem Commands (Root Directory)
14
+
15
+ - `bundle install`: Install dependencies.
16
+ - `rake spec`: Run all gem tests.
17
+ - `bin/console`: Interactive session with the gem loaded.
18
+ - `bundle exec rubocop`: Run linting.
19
+
20
+ ## Demo App Commands (`demo_app/`)
21
+
22
+ - `bin/setup`: Initial installation and database preparation.
23
+ - `bin/dev`: Start the Rails server.
24
+ - `bin/rails s`: Standard Rails server command.
25
+ - `bin/demo_scenarios.rb`: Script to run various reactor scenarios.
26
+ - `bundle exec rake demo:flush_redis`: Clear the Redis storage.
27
+ - `bundle exec rake demo:payment_workflow`: Run the payment workflow with all failure scenarios.
28
+ - `bundle exec rake demo:order_processing`: Run the order processing reactor with all failure scenarios.
29
+
30
+ ## GUI Commands (`gui/`)
31
+
32
+ - `npm install`: Install dependencies.
33
+ - `npm run dev`: Start the Vite development server.
34
+ - `npm run build`: Build for production.
35
+ - `npm test`: Run Vitest tests.
36
+ - `npm run lint`: Run ESLint.
37
+
38
+ ## Debugging and Verification
39
+
40
+ ### Debugging Reactors
41
+ - **Check Redis State**: Use `demo:flush_redis` rake task to reset state.
42
+ - **Seed reactors**: Use `demo:order_processing`, `demo:payment_workflow` or similars rake tasks to seed reactors.
43
+ - **Sidekiq Logs**: Monitor Sidekiq output to debug asynchronous step execution.
44
+ - **Fail-at Scenarios**: Use the `fail_at` parameter in demo reactors to simulate errors at specific steps.
45
+ - **Interactive Console**: Use `bin/console` (root) or `bundle exec rails c` (demo_app) to inspect reactor objects and context.
46
+
47
+ ### Verifying Results
48
+ - **Success/Failure Status**: Check the `result.success?` or `result.failure?` flags.
49
+ - **Step Results**: Inspect `result.value` for successful results or `result.error` for failure details.
50
+ - **Intermediate Results**: Check `result.intermediate_results` for data from individual steps (useful for async reactors).
51
+ - **GUI Visualization**: Use the GUI at `http://localhost:3000/ruby_reactor` (when mounted in demo_app) to visually inspect the DAG and step statuses.
52
+
53
+ ### Automated Verification
54
+ - Run `rake spec` in the root directory to ensure no regressions in the core library.
55
+ - Run `npm test` in the `gui/` directory to verify UI components.
56
+ - Run `bin/demo_scenarios.rb` to verify integration across multiple reactor types.
57
+
58
+ ### Debugging and refreshing reactors and inspecting GUI Using demo_app
59
+ 1. **Kill rails server if running**: Find rails process and kill it.
60
+ 2. **Run redis server**: Use docker to run redis server if is not running.
61
+ 3. **Run the reactor we are using for test feature in development**: Use demo_app `demo:order_processing` or `demo:payment_workflow` rake task to reset state.
62
+ 4. **Run sidekiq**: Use `bundle exec sidekiq` in `demo_app` directory to start Sidekiq and review logs.
63
+ 5. **Build GUI**: Use `rake build:ui` to build the GUI.
64
+ 6. **Start Rails server**: In `demo_app` directory run `bin/rails s` to start the Rails server.
65
+ 7. **Navigate to GUI**: Open `http://localhost:3000/ruby_reactor` in your browser to access the GUI.
66
+ 8. **Run tests**: bundle exec rspec, all tests should pass.
data/llms.txt ADDED
@@ -0,0 +1,7 @@
1
+ # Ruby Reactor
2
+
3
+ A dynamic, dependency-resolving saga orchestrator for Ruby. Ruby Reactor implements the Saga pattern with compensation-based error handling and DAG-based execution planning. It leverages Sidekiq for asynchronous execution and Redis for state persistence.
4
+
5
+ - [Main Documentation](documentation/core_concepts.md)
6
+ - [Full LLM Guide](llms-full.txt): Comprehensive command reference, debugging strategies, and verification workflows.
7
+ - [GitHub Repository](https://github.com/arturictus/ruby_reactor)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_reactor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Artur
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-12-02 00:00:00.000000000 Z
11
+ date: 2026-01-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-validation
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '5.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: roda
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: sidekiq
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -90,7 +104,40 @@ files:
90
104
  - documentation/examples/order_processing.md
91
105
  - documentation/examples/payment_processing.md
92
106
  - documentation/getting_started.md
107
+ - documentation/images/failed_order_processing.png
108
+ - documentation/images/payment_workflow.png
109
+ - documentation/interrupts.md
93
110
  - documentation/retry_configuration.md
111
+ - gui/.gitignore
112
+ - gui/README.md
113
+ - gui/eslint.config.js
114
+ - gui/index.html
115
+ - gui/package-lock.json
116
+ - gui/package.json
117
+ - gui/postcss.config.js
118
+ - gui/public/vite.svg
119
+ - gui/src/App.css
120
+ - gui/src/App.tsx
121
+ - gui/src/assets/react.svg
122
+ - gui/src/components/DagVisualizer.tsx
123
+ - gui/src/components/Dashboard.tsx
124
+ - gui/src/components/ErrorBoundary.tsx
125
+ - gui/src/components/ReactorDetail.tsx
126
+ - gui/src/components/StepInspector.tsx
127
+ - gui/src/components/__tests__/DagVisualizer.test.tsx
128
+ - gui/src/components/__tests__/ReactorDetail.test.tsx
129
+ - gui/src/components/__tests__/StepInspector.test.tsx
130
+ - gui/src/globals.d.ts
131
+ - gui/src/index.css
132
+ - gui/src/lib/utils.ts
133
+ - gui/src/main.tsx
134
+ - gui/src/test/setup.ts
135
+ - gui/tailwind.config.js
136
+ - gui/tsconfig.app.json
137
+ - gui/tsconfig.json
138
+ - gui/tsconfig.node.json
139
+ - gui/vite.config.ts
140
+ - gui/vitest.config.ts
94
141
  - lib/ruby_reactor.rb
95
142
  - lib/ruby_reactor/async_router.rb
96
143
  - lib/ruby_reactor/configuration.rb
@@ -98,6 +145,8 @@ files:
98
145
  - lib/ruby_reactor/context_serializer.rb
99
146
  - lib/ruby_reactor/dependency_graph.rb
100
147
  - lib/ruby_reactor/dsl/compose_builder.rb
148
+ - lib/ruby_reactor/dsl/interrupt_builder.rb
149
+ - lib/ruby_reactor/dsl/interrupt_step_config.rb
101
150
  - lib/ruby_reactor/dsl/map_builder.rb
102
151
  - lib/ruby_reactor/dsl/reactor.rb
103
152
  - lib/ruby_reactor/dsl/step_builder.rb
@@ -120,12 +169,14 @@ files:
120
169
  - lib/ruby_reactor/executor/result_handler.rb
121
170
  - lib/ruby_reactor/executor/retry_manager.rb
122
171
  - lib/ruby_reactor/executor/step_executor.rb
172
+ - lib/ruby_reactor/interrupt_result.rb
123
173
  - lib/ruby_reactor/map/collector.rb
124
174
  - lib/ruby_reactor/map/element_executor.rb
125
175
  - lib/ruby_reactor/map/execution.rb
126
176
  - lib/ruby_reactor/map/helpers.rb
127
177
  - lib/ruby_reactor/max_retries_exhausted_failure.rb
128
178
  - lib/ruby_reactor/reactor.rb
179
+ - lib/ruby_reactor/registry.rb
129
180
  - lib/ruby_reactor/retry_context.rb
130
181
  - lib/ruby_reactor/retry_queued_result.rb
131
182
  - lib/ruby_reactor/sidekiq_workers/map_collector_worker.rb
@@ -143,10 +194,20 @@ files:
143
194
  - lib/ruby_reactor/template/input.rb
144
195
  - lib/ruby_reactor/template/result.rb
145
196
  - lib/ruby_reactor/template/value.rb
197
+ - lib/ruby_reactor/utils/code_extractor.rb
146
198
  - lib/ruby_reactor/validation/base.rb
147
199
  - lib/ruby_reactor/validation/input_validator.rb
148
200
  - lib/ruby_reactor/validation/schema_builder.rb
149
201
  - lib/ruby_reactor/version.rb
202
+ - lib/ruby_reactor/web/api.rb
203
+ - lib/ruby_reactor/web/application.rb
204
+ - lib/ruby_reactor/web/config.ru
205
+ - lib/ruby_reactor/web/public/assets/index-VdeLgH9k.js
206
+ - lib/ruby_reactor/web/public/assets/index-_z-6BvuM.css
207
+ - lib/ruby_reactor/web/public/index.html
208
+ - lib/ruby_reactor/web/public/vite.svg
209
+ - llms-full.txt
210
+ - llms.txt
150
211
  - sig/ruby_reactor.rbs
151
212
  homepage: https://github.com/arturictus/ruby_reactor
152
213
  licenses: []