migflow 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 (58) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +44 -0
  3. data/.ruby-version +1 -0
  4. data/CHANGELOG.md +45 -0
  5. data/CLAUDE.md +124 -0
  6. data/CODE_OF_CONDUCT.md +132 -0
  7. data/CONTRIBUTING.md +157 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +218 -0
  10. data/Rakefile +11 -0
  11. data/SECURITY.md +27 -0
  12. data/app/assets/migflow/app.css +1 -0
  13. data/app/assets/migflow/app.js +28 -0
  14. data/app/assets/migflow/index.html +14 -0
  15. data/app/assets/migflow/vite.svg +1 -0
  16. data/app/controllers/migflow/api/diff_controller.rb +73 -0
  17. data/app/controllers/migflow/api/migrations_controller.rb +97 -0
  18. data/app/controllers/migflow/application_controller.rb +62 -0
  19. data/app/views/migflow/application/index.html.erb +16 -0
  20. data/config/routes.rb +10 -0
  21. data/docs/architecture.md +130 -0
  22. data/lib/migflow/analyzers/audit_analyzer.rb +58 -0
  23. data/lib/migflow/analyzers/rules/base_rule.rb +32 -0
  24. data/lib/migflow/analyzers/rules/dangerous_migration_rule.rb +44 -0
  25. data/lib/migflow/analyzers/rules/missing_foreign_key_rule.rb +30 -0
  26. data/lib/migflow/analyzers/rules/missing_index_rule.rb +32 -0
  27. data/lib/migflow/analyzers/rules/missing_timestamps_rule.rb +38 -0
  28. data/lib/migflow/analyzers/rules/null_column_without_default_rule.rb +46 -0
  29. data/lib/migflow/analyzers/rules/string_without_limit_rule.rb +28 -0
  30. data/lib/migflow/app/assets/migflow/app.css +1 -0
  31. data/lib/migflow/app/assets/migflow/app.js +17 -0
  32. data/lib/migflow/app/assets/migflow/index.html +14 -0
  33. data/lib/migflow/app/assets/migflow/vite.svg +1 -0
  34. data/lib/migflow/configuration.rb +36 -0
  35. data/lib/migflow/engine.rb +14 -0
  36. data/lib/migflow/models/migration_snapshot.rb +15 -0
  37. data/lib/migflow/models/schema_diff.rb +9 -0
  38. data/lib/migflow/models/warning.rb +7 -0
  39. data/lib/migflow/parsers/migration_parser.rb +52 -0
  40. data/lib/migflow/parsers/schema_parser.rb +105 -0
  41. data/lib/migflow/reporters/json_reporter.rb +13 -0
  42. data/lib/migflow/reporters/markdown_reporter.rb +58 -0
  43. data/lib/migflow/reporters.rb +38 -0
  44. data/lib/migflow/services/diff_builder.rb +77 -0
  45. data/lib/migflow/services/migration_dsl_scanner.rb +161 -0
  46. data/lib/migflow/services/migration_summary_builder.rb +43 -0
  47. data/lib/migflow/services/report_generator.rb +76 -0
  48. data/lib/migflow/services/risk_scorer.rb +38 -0
  49. data/lib/migflow/services/schema_builder.rb +25 -0
  50. data/lib/migflow/services/schema_patch_builder.rb +237 -0
  51. data/lib/migflow/services/scoped_migration_warnings.rb +93 -0
  52. data/lib/migflow/services/snapshot_builder.rb +542 -0
  53. data/lib/migflow/services/touched_tables_from_migration.rb +60 -0
  54. data/lib/migflow/version.rb +5 -0
  55. data/lib/migflow.rb +20 -0
  56. data/lib/tasks/migflow.rake +31 -0
  57. data/sig/migflow.rbs +3 -0
  58. metadata +124 -0
data/README.md ADDED
@@ -0,0 +1,218 @@
1
+ # Migflow
2
+
3
+ **Migration intelligence for Rails teams.**
4
+
5
+ Migflow is a Rails engine that mounts at `/migflow` and gives your team a visual timeline, schema diffs, and audit warnings — so you can understand migration impact before it reaches production.
6
+
7
+ [![CI](https://img.shields.io/github/actions/workflow/status/jv4lentim/migflow/ci.yml?branch=main&label=CI&style=flat)](https://github.com/jv4lentim/migflow/actions/workflows/ci.yml)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE.txt)
9
+ [![Ruby](https://img.shields.io/badge/Ruby-%3E%3D%203.2-red)](https://www.ruby-lang.org/)
10
+ [![Rails](https://img.shields.io/badge/Rails-%3E%3D%207.0-cc0000)](https://rubyonrails.org/)
11
+
12
+ <img width="1281" height="484" alt="Kapture 2026-04-22 at 12 47 11" src="https://github.com/user-attachments/assets/25d10fb2-06c9-4476-92e8-bf77986948ed" />
13
+
14
+ ---
15
+
16
+ ## What it does
17
+
18
+ - **Timeline** — browse every migration in order, with version, name, and a one-line summary.
19
+ - **Detail view** — inspect raw migration content, schema snapshot, and audit warnings side by side.
20
+ - **Schema diff** — focused and full diff hunks powered by `schema.rb` patches between versions.
21
+ - **Compare mode** — pick any two migration versions and see exactly what changed.
22
+ - **Schema graph** — interactive ERD with tables, columns, foreign keys, and diff highlights.
23
+ - **CI report** — generate a Markdown or JSON report of all migrations and gate your pipeline on risk score.
24
+
25
+ ## Requirements
26
+
27
+ - Ruby >= 3.2
28
+ - Rails 7.0 or newer
29
+ - A Rails app with migrations in `db/migrate` and a `db/schema.rb`
30
+
31
+ ## Compatibility
32
+
33
+ Tested in CI against every combination below:
34
+
35
+ | | Rails 7.0 | Rails 7.1 | Rails 7.2 | Rails 8.1 |
36
+ |------------|:---------:|:---------:|:---------:|:---------:|
37
+ | Ruby 3.2 | ✅ | ✅ | ✅ | ✅ |
38
+ | Ruby 3.3 | ✅ | ✅ | ✅ | ✅ |
39
+ | Ruby 3.4 | ✅ | ✅ | ✅ | ✅ |
40
+ | Ruby 4.0 | ✅ | ✅ | ✅ | ✅ |
41
+
42
+ ## Installation
43
+
44
+ Add Migflow to your `Gemfile` (Git source until the first RubyGems release):
45
+
46
+ ```ruby
47
+ gem "migflow", git: "https://github.com/jv4lentim/migflow"
48
+ ```
49
+
50
+ ```bash
51
+ bundle install
52
+ ```
53
+
54
+ Mount the engine in your routes:
55
+
56
+ ```ruby
57
+ # config/routes.rb
58
+ mount Migflow::Engine, at: "/migflow"
59
+ ```
60
+
61
+ Start your app and open [http://localhost:3000/migflow](http://localhost:3000/migflow).
62
+
63
+ ## Configuration
64
+
65
+ All options are set in an initializer:
66
+
67
+ ```ruby
68
+ # config/initializers/migflow.rb
69
+ Migflow.configure do |config|
70
+ # ...
71
+ end
72
+ ```
73
+
74
+ ### Options
75
+
76
+ | Option | Default | Description |
77
+ |--------|---------|-------------|
78
+ | `migrations_path` | `db/migrate` | Path to the migrations directory |
79
+ | `schema_path` | `db/schema.rb` | Path to the schema file |
80
+ | `enabled_rules` | `:all` | Audit rules to run. Pass an array of rule name symbols to enable a subset, or `:all` to run every rule |
81
+ | `expose_raw_content` | `true` | Whether to include the migration source code in the API response. Set to `false` to hide it |
82
+ | `parent_controller` | `"ActionController::Base"` | Controller class Migflow inherits from. Set to your app's `ApplicationController` to inherit authentication helpers |
83
+ | `authentication_hook` | `nil` | A lambda run as a `before_action` on every Migflow request. Use it to enforce authentication |
84
+ | `unauthenticated_redirect` | `nil` | A lambda returning the path to redirect to when authentication fails. Required when `authentication_hook` is set, because host app route helpers must be accessed via `main_app.<helper>` inside a mounted engine |
85
+
86
+ ### Authentication
87
+
88
+ Migflow has no authentication out of the box. To protect the dashboard, set `parent_controller` to inherit your app's auth helpers, provide an `authentication_hook` to enforce the check, and set `unauthenticated_redirect` to tell Migflow where to send unauthenticated requests.
89
+
90
+ **Rails 8 built-in Authentication**
91
+
92
+ ```ruby
93
+ Migflow.configure do |config|
94
+ config.parent_controller = "ApplicationController"
95
+ config.authentication_hook = -> { require_authentication }
96
+ config.unauthenticated_redirect = -> { main_app.new_session_path }
97
+ end
98
+ ```
99
+
100
+ **Devise**
101
+
102
+ ```ruby
103
+ Migflow.configure do |config|
104
+ config.parent_controller = "ApplicationController"
105
+ config.authentication_hook = -> { authenticate_admin! }
106
+ config.unauthenticated_redirect = -> { main_app.new_admin_session_path }
107
+ end
108
+ ```
109
+
110
+ ## API
111
+
112
+ The frontend talks to these JSON endpoints under `/migflow/api`:
113
+
114
+ | Method | Path | Description |
115
+ |--------|------|-------------|
116
+ | `GET` | `/migrations` | List all migrations for the timeline |
117
+ | `GET` | `/migrations/:version` | Migration detail — warnings and schema patch |
118
+ | `GET` | `/diff?from=:version&to=:version` | Schema diff between two versions |
119
+
120
+ ## CI report
121
+
122
+ Run the analysis from the command line without starting a server:
123
+
124
+ ```bash
125
+ # Markdown summary (default)
126
+ bundle exec rails migflow:report
127
+
128
+ # JSON output for downstream tooling
129
+ bundle exec rails migflow:report FORMAT=json
130
+
131
+ # Gate: exit 1 if any migration has risk level high or above
132
+ bundle exec rails migflow:report FAIL_ON=high
133
+
134
+ # Gate: exit 1 if any migration scores 40 or above
135
+ bundle exec rails migflow:report FAIL_ON=40
136
+
137
+ # Write to a file instead of stdout
138
+ bundle exec rails migflow:report FORMAT=json OUTPUT=migflow-report.json
139
+ ```
140
+
141
+ `FAIL_ON` accepts a level name (`low`, `medium`, `high`) or any integer score. Level names map to their minimum boundary (`high` → 71, `medium` → 31, `low` → 1), so `FAIL_ON=medium` catches medium **and** high migrations.
142
+
143
+ ### GitHub Actions
144
+
145
+ ```yaml
146
+ - name: Migration analysis summary
147
+ run: bundle exec rails migflow:report FORMAT=markdown >> $GITHUB_STEP_SUMMARY
148
+
149
+ - name: Gate on high risk
150
+ run: bundle exec rails migflow:report FAIL_ON=high
151
+ ```
152
+
153
+ A ready-made workflow that triggers on pull requests touching `db/migrate/` is included at `.github/workflows/ci-report.yml`.
154
+
155
+ ## Development
156
+
157
+ **Prerequisites:** Ruby 3.3, Node 22.
158
+
159
+ ```bash
160
+ git clone https://github.com/jv4lentim/migflow.git
161
+ cd migflow
162
+ bin/setup
163
+ ```
164
+
165
+ `bin/setup` installs Ruby and frontend dependencies. After that:
166
+
167
+ ```bash
168
+ # Run tests
169
+ bundle exec rake spec
170
+
171
+ # Run linter
172
+ bundle exec rubocop
173
+
174
+ # Build frontend assets
175
+ cd frontend && npm run build
176
+
177
+ # Frontend dev server with hot reload (http://localhost:5173)
178
+ cd frontend && npm run dev
179
+ ```
180
+
181
+ After rebuilding frontend assets, restart your Rails server to pick up the changes.
182
+
183
+ **Testing against a local Rails app:**
184
+
185
+ ```ruby
186
+ # In your app's Gemfile:
187
+ gem "migflow", path: "../migflow"
188
+ ```
189
+
190
+ ## Limitations
191
+
192
+ - **Read-only.** Migflow only reads `db/migrate/` and `db/schema.rb` — it never runs migrations or writes to the database.
193
+ - **`schema.rb` required.** Projects using `structure.sql` are not supported yet.
194
+ - **Regex-based DSL parsing.** `SnapshotBuilder` replays migration DSL calls with a regex scanner. Highly dynamic migrations (metaprogramming, loops, `execute` with raw SQL) may produce incomplete snapshots.
195
+ - **No authentication out of the box.** See [Authentication](#authentication) to protect the dashboard before deploying to a shared environment.
196
+ - **Single-app only.** There is no support for multi-database setups or comparing migrations across separate Rails apps.
197
+
198
+ ## Roadmap
199
+
200
+ Planned in rough priority order:
201
+
202
+ - [ ] `structure.sql` support
203
+ - [ ] Baseline / waiver system — suppress known warnings explicitly and traceably
204
+ - [ ] Cross-branch comparison — diff migrations between two git branches without switching
205
+
206
+ Have an idea? [Open a feature request](https://github.com/jv4lentim/migflow/issues/new?template=feature_request.yml).
207
+
208
+ ## Contributing
209
+
210
+ Issues and pull requests are welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md) for the full guide.
211
+
212
+ ## Code of Conduct
213
+
214
+ This project follows the [Contributor Covenant](./CODE_OF_CONDUCT.md).
215
+
216
+ ## License
217
+
218
+ Migflow is released under the [MIT License](./LICENSE.txt).
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+ RuboCop::RakeTask.new
10
+
11
+ task default: %i[spec rubocop]
data/SECURITY.md ADDED
@@ -0,0 +1,27 @@
1
+ # Security Policy
2
+
3
+ ## Supported versions
4
+
5
+ Only the latest released version of Migflow receives security fixes.
6
+
7
+ ## Reporting a vulnerability
8
+
9
+ Please **do not** open a public GitHub issue for security vulnerabilities.
10
+
11
+ Send a report to **joaovictorvalentim@gmail.com** with:
12
+
13
+ - A description of the vulnerability and its potential impact
14
+ - Steps to reproduce or a proof-of-concept
15
+ - Any suggested fix, if you have one
16
+
17
+ You can expect an acknowledgement within **72 hours** and a resolution timeline within **14 days** of the initial report, depending on severity.
18
+
19
+ ## Scope
20
+
21
+ Migflow is a Rails engine mounted inside a host application. Reports are in scope if they affect:
22
+
23
+ - The engine's REST API endpoints (`/migflow/api/*`)
24
+ - The Rake task (`migflow:report`) and its output
25
+ - Any data exposure through migration content or schema information
26
+
27
+ Issues in the host Rails application, its database, or third-party dependencies are generally out of scope unless Migflow directly introduces the vulnerability.
@@ -0,0 +1 @@
1
+ @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-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking: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-duration:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-white:#fff;--spacing:.25rem;--container-sm:24rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5 / 2.25);--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-wider:.05em;--tracking-widest:.1em;--radius-md:.375rem;--radius-lg:.5rem;--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--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%;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}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::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]){appearance:button}::file-selector-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}.absolute{position:absolute}.relative{position:relative}.inset-0{inset:calc(var(--spacing) * 0)}.end{inset-inline-end:var(--spacing)}.top-0{top:calc(var(--spacing) * 0)}.right-0{right:calc(var(--spacing) * 0)}.left-0{left:calc(var(--spacing) * 0)}.z-10{z-index:10}.mx-3{margin-inline:calc(var(--spacing) * 3)}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.ml-1{margin-left:calc(var(--spacing) * 1)}.box-border{box-sizing:border-box}.flex{display:flex}.hidden{display:none}.inline-flex{display:inline-flex}.table{display:table}.\!h-2{height:calc(var(--spacing) * 2)!important}.h-1{height:calc(var(--spacing) * 1)}.h-1\.5{height:calc(var(--spacing) * 1.5)}.h-2{height:calc(var(--spacing) * 2)}.h-2\.5{height:calc(var(--spacing) * 2.5)}.h-3{height:calc(var(--spacing) * 3)}.h-12{height:calc(var(--spacing) * 12)}.h-32{height:calc(var(--spacing) * 32)}.h-\[calc\(100\%-41px\)\]{height:calc(100% - 41px)}.h-full{height:100%}.h-screen{height:100vh}.max-h-48{max-height:calc(var(--spacing) * 48)}.min-h-0{min-height:calc(var(--spacing) * 0)}.min-h-\[120px\]{min-height:120px}.\!w-2{width:calc(var(--spacing) * 2)!important}.w-1{width:calc(var(--spacing) * 1)}.w-1\/2{width:50%}.w-2{width:calc(var(--spacing) * 2)}.w-3{width:calc(var(--spacing) * 3)}.w-3\/4{width:75%}.w-12{width:calc(var(--spacing) * 12)}.w-14{width:calc(var(--spacing) * 14)}.w-full{width:100%}.max-w-\[min\(40vw\,320px\)\]{max-width:min(40vw,320px)}.max-w-sm{max-width:var(--container-sm)}.min-w-0{min-width:calc(var(--spacing) * 0)}.min-w-2{min-width:calc(var(--spacing) * 2)}.flex-1{flex:1}.shrink-0{flex-shrink:0}.rotate-180{rotate:180deg}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.animate-pulse{animation:var(--animate-pulse)}.cursor-col-resize{cursor:col-resize}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.cursor-row-resize{cursor:row-resize}.flex-col{flex-direction:column}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.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)}: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-1\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1.5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1.5) * 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-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.\!rounded-md{border-radius:var(--radius-md)!important}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.\!border{border-style:var(--tw-border-style)!important;border-width:1px!important}.border{border-style:var(--tw-border-style);border-width:1px}.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-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-l-2{border-left-style:var(--tw-border-style);border-left-width:2px}.\!border-\[\#484F58\]{border-color:#484f58!important}.\!border-\[\#30363D\]{border-color:#30363d!important}.border-\[\#3FB950\]\/30{border-color:#3fb9504d}.border-\[\#58A6FF\]{border-color:#58a6ff}.border-\[\#30363D\]{border-color:#30363d}.border-\[\#D29922\]\/30{border-color:#d299224d}.border-\[\#E8862A\]\/30{border-color:#e8862a4d}.border-\[\#F85149\]\/30{border-color:#f851494d}.border-l-\[\#58A6FF\]{border-left-color:#58a6ff}.\!bg-\[\#0D1117\]{background-color:#0d1117!important}.\!bg-\[\#161B22\]{background-color:#161b22!important}.\!bg-\[\#30363D\]{background-color:#30363d!important}.bg-\[\#0D1117\]{background-color:#0d1117}.bg-\[\#1A3A1A\]{background-color:#1a3a1a}.bg-\[\#2D1B1B\]{background-color:#2d1b1b}.bg-\[\#2D1F0A\]{background-color:#2d1f0a}.bg-\[\#2D2414\]{background-color:#2d2414}.bg-\[\#3FB950\]{background-color:#3fb950}.bg-\[\#58A6FF\]{background-color:#58a6ff}.bg-\[\#161B22\]{background-color:#161b22}.bg-\[\#21262D\]{background-color:#21262d}.bg-\[\#30363D\]{background-color:#30363d}.bg-\[\#D29922\]{background-color:#d29922}.bg-\[\#E8862A\]{background-color:#e8862a}.bg-\[\#F85149\]{background-color:#f85149}.p-0\.5{padding:calc(var(--spacing) * .5)}.p-1{padding:calc(var(--spacing) * 1)}.p-1\.5{padding:calc(var(--spacing) * 1.5)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.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-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.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-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.pt-3{padding-top:calc(var(--spacing) * 3)}.pb-2{padding-bottom:calc(var(--spacing) * 2)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.text-center{text-align:center}.text-left{text-align:left}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--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-\[9px\]{font-size:9px}.text-\[10px\]{font-size:10px}.leading-5{--tw-leading:calc(var(--spacing) * 5);line-height:calc(var(--spacing) * 5)}.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)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.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-\[\#E6EDF3\]{color:#e6edf3!important}.text-\[\#0D1117\]{color:#0d1117}.text-\[\#3FB950\]{color:#3fb950}.text-\[\#7D8590\]{color:#7d8590}.text-\[\#58A6FF\]{color:#58a6ff}.text-\[\#D29922\]{color:#d29922}.text-\[\#E6EDF3\]{color:#e6edf3}.text-\[\#E8862A\]{color:#e8862a}.text-\[\#F85149\]{color:#f85149}.text-white{color:var(--color-white)}.uppercase{text-transform:uppercase}.line-through{text-decoration-line:line-through}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.opacity-70{opacity:.7}.shadow-\[inset_0_-2px_0_0_\#58A6FF\]{--tw-shadow:inset 0 -2px 0 0 var(--tw-shadow-color,#58a6ff);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)}.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)}.ring-\[\#58A6FF\]{--tw-ring-color:#58a6ff}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.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))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-150{--tw-duration:.15s;transition-duration:.15s}.select-none{-webkit-user-select:none;user-select:none}@media(hover:hover){.hover\:border-\[\#7D8590\]:hover{border-color:#7d8590}.hover\:bg-\[\#0D1117\]:hover{background-color:#0d1117}.hover\:bg-\[\#58A6FF\]:hover{background-color:#58a6ff}.hover\:bg-\[\#161B22\]:hover{background-color:#161b22}.hover\:bg-\[\#21262D\]:hover{background-color:#21262d}.hover\:bg-\[\#30363D\]:hover{background-color:#30363d}.hover\:text-\[\#58A6FF\]:hover{color:#58a6ff}.hover\:text-\[\#E6EDF3\]:hover{color:#e6edf3}}.disabled\:opacity-40:disabled{opacity:.4}}*{box-sizing:border-box}html,body,#root{color:#e6edf3;background-color:#0d1117;height:100%;margin:0;padding:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:#0d1117}::-webkit-scrollbar-thumb{background:#30363d;border-radius:3px}::-webkit-scrollbar-thumb:hover{background:#484f58}.react-flow__node{font-size:12px}.react-flow__controls{background:#161b22!important;border:1px solid #30363d!important;border-radius:6px!important}.react-flow__controls-button{color:#e6edf3!important;background:#161b22!important;border-bottom:1px solid #30363d!important}.react-flow__controls-button:hover{background:#0d1117!important}@keyframes edgeFlow{to{stroke-dashoffset:-20px}}.edge-flow{stroke-dasharray:5 5;animation:.5s linear infinite edgeFlow}.react-flow__edge-path{cursor:pointer}.migflow-schema-diff{--diff-background-color:#161b22;--diff-text-color:#e6edf3;--diff-selection-background-color:#1f2a3a;--diff-selection-text-color:#e6edf3;--diff-gutter-insert-background-color:#113218;--diff-gutter-insert-text-color:#56d364;--diff-gutter-delete-background-color:#3a1618;--diff-gutter-delete-text-color:#ff6b6b;--diff-code-insert-background-color:#113218;--diff-code-insert-text-color:#e6edf3;--diff-code-delete-background-color:#3a1618;--diff-code-delete-text-color:#e6edf3;--diff-code-insert-edit-background-color:#1a4724;--diff-code-delete-edit-background-color:#4a1d20;--diff-code-selected-background-color:#1f2a3a;--diff-code-selected-text-color:#e6edf3}.migflow-schema-diff .diff-gutter-hunk,.migflow-schema-diff .diff-code-hunk{color:#9ca3af;background:#111827}.migflow-schema-diff .diff tbody+tbody .diff-gutter,.migflow-schema-diff .diff tbody+tbody .diff-code{border-top:12px solid #161b22}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-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-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-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))}:root{--diff-background-color:initial;--diff-text-color:initial;--diff-font-family:Consolas,Courier,monospace;--diff-selection-background-color:#b3d7ff;--diff-selection-text-color:var(--diff-text-color);--diff-gutter-insert-background-color:#d6fedb;--diff-gutter-insert-text-color:var(--diff-text-color);--diff-gutter-delete-background-color:#fadde0;--diff-gutter-delete-text-color:var(--diff-text-color);--diff-gutter-selected-background-color:#fffce0;--diff-gutter-selected-text-color:var(--diff-text-color);--diff-code-insert-background-color:#eaffee;--diff-code-insert-text-color:var(--diff-text-color);--diff-code-delete-background-color:#fdeff0;--diff-code-delete-text-color:var(--diff-text-color);--diff-code-insert-edit-background-color:#c0dc91;--diff-code-insert-edit-text-color:var(--diff-text-color);--diff-code-delete-edit-background-color:#f39ea2;--diff-code-delete-edit-text-color:var(--diff-text-color);--diff-code-selected-background-color:#fffce0;--diff-code-selected-text-color:var(--diff-text-color);--diff-omit-gutter-line-color:#cb2a1d}.diff{background-color:var(--diff-background-color);border-collapse:collapse;color:var(--diff-text-color);table-layout:fixed;width:100%}.diff::-moz-selection{background-color:#b3d7ff;background-color:var(--diff-selection-background-color);color:var(--diff-text-color);color:var(--diff-selection-text-color)}.diff::selection{background-color:#b3d7ff;background-color:var(--diff-selection-background-color);color:var(--diff-text-color);color:var(--diff-selection-text-color)}.diff td{padding-bottom:0;padding-top:0;vertical-align:top}.diff-line{font-family:Consolas,Courier,monospace;font-family:var(--diff-font-family);line-height:1.5}.diff-gutter>a{color:inherit;display:block}.diff-gutter{cursor:pointer;padding:0 1ch;text-align:right;-webkit-user-select:none;-moz-user-select:none;user-select:none}.diff-gutter-insert{background-color:#d6fedb;background-color:var(--diff-gutter-insert-background-color);color:var(--diff-text-color);color:var(--diff-gutter-insert-text-color)}.diff-gutter-delete{background-color:#fadde0;background-color:var(--diff-gutter-delete-background-color);color:var(--diff-text-color);color:var(--diff-gutter-delete-text-color)}.diff-gutter-omit{cursor:default}.diff-gutter-selected{background-color:#fffce0;background-color:var(--diff-gutter-selected-background-color);color:var(--diff-text-color);color:var(--diff-gutter-selected-text-color)}.diff-code{word-wrap:break-word;padding:0 0 0 .5em;white-space:pre-wrap;word-break:break-all}.diff-code-edit{color:inherit}.diff-code-insert{background-color:#eaffee;background-color:var(--diff-code-insert-background-color);color:var(--diff-text-color);color:var(--diff-code-insert-text-color)}.diff-code-insert .diff-code-edit{background-color:#c0dc91;background-color:var(--diff-code-insert-edit-background-color);color:var(--diff-text-color);color:var(--diff-code-insert-edit-text-color)}.diff-code-delete{background-color:#fdeff0;background-color:var(--diff-code-delete-background-color);color:var(--diff-text-color);color:var(--diff-code-delete-text-color)}.diff-code-delete .diff-code-edit{background-color:#f39ea2;background-color:var(--diff-code-delete-edit-background-color);color:var(--diff-text-color);color:var(--diff-code-delete-edit-text-color)}.diff-code-selected{background-color:#fffce0;background-color:var(--diff-code-selected-background-color);color:var(--diff-text-color);color:var(--diff-code-selected-text-color)}.diff-widget-content{vertical-align:top}.diff-gutter-col{width:7ch}.diff-gutter-omit{height:0}.diff-gutter-omit:before{background-color:#cb2a1d;background-color:var(--diff-omit-gutter-line-color);content:" ";display:block;height:100%;margin-left:4.6ch;overflow:hidden;white-space:pre;width:2px}.diff-decoration{line-height:1.5;-webkit-user-select:none;-moz-user-select:none;user-select:none}.diff-decoration-content{font-family:Consolas,Courier,monospace;font-family:var(--diff-font-family);padding:0}