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.
- checksums.yaml +7 -0
- data/.rubocop.yml +44 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +45 -0
- data/CLAUDE.md +124 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/CONTRIBUTING.md +157 -0
- data/LICENSE.txt +21 -0
- data/README.md +218 -0
- data/Rakefile +11 -0
- data/SECURITY.md +27 -0
- data/app/assets/migflow/app.css +1 -0
- data/app/assets/migflow/app.js +28 -0
- data/app/assets/migflow/index.html +14 -0
- data/app/assets/migflow/vite.svg +1 -0
- data/app/controllers/migflow/api/diff_controller.rb +73 -0
- data/app/controllers/migflow/api/migrations_controller.rb +97 -0
- data/app/controllers/migflow/application_controller.rb +62 -0
- data/app/views/migflow/application/index.html.erb +16 -0
- data/config/routes.rb +10 -0
- data/docs/architecture.md +130 -0
- data/lib/migflow/analyzers/audit_analyzer.rb +58 -0
- data/lib/migflow/analyzers/rules/base_rule.rb +32 -0
- data/lib/migflow/analyzers/rules/dangerous_migration_rule.rb +44 -0
- data/lib/migflow/analyzers/rules/missing_foreign_key_rule.rb +30 -0
- data/lib/migflow/analyzers/rules/missing_index_rule.rb +32 -0
- data/lib/migflow/analyzers/rules/missing_timestamps_rule.rb +38 -0
- data/lib/migflow/analyzers/rules/null_column_without_default_rule.rb +46 -0
- data/lib/migflow/analyzers/rules/string_without_limit_rule.rb +28 -0
- data/lib/migflow/app/assets/migflow/app.css +1 -0
- data/lib/migflow/app/assets/migflow/app.js +17 -0
- data/lib/migflow/app/assets/migflow/index.html +14 -0
- data/lib/migflow/app/assets/migflow/vite.svg +1 -0
- data/lib/migflow/configuration.rb +36 -0
- data/lib/migflow/engine.rb +14 -0
- data/lib/migflow/models/migration_snapshot.rb +15 -0
- data/lib/migflow/models/schema_diff.rb +9 -0
- data/lib/migflow/models/warning.rb +7 -0
- data/lib/migflow/parsers/migration_parser.rb +52 -0
- data/lib/migflow/parsers/schema_parser.rb +105 -0
- data/lib/migflow/reporters/json_reporter.rb +13 -0
- data/lib/migflow/reporters/markdown_reporter.rb +58 -0
- data/lib/migflow/reporters.rb +38 -0
- data/lib/migflow/services/diff_builder.rb +77 -0
- data/lib/migflow/services/migration_dsl_scanner.rb +161 -0
- data/lib/migflow/services/migration_summary_builder.rb +43 -0
- data/lib/migflow/services/report_generator.rb +76 -0
- data/lib/migflow/services/risk_scorer.rb +38 -0
- data/lib/migflow/services/schema_builder.rb +25 -0
- data/lib/migflow/services/schema_patch_builder.rb +237 -0
- data/lib/migflow/services/scoped_migration_warnings.rb +93 -0
- data/lib/migflow/services/snapshot_builder.rb +542 -0
- data/lib/migflow/services/touched_tables_from_migration.rb +60 -0
- data/lib/migflow/version.rb +5 -0
- data/lib/migflow.rb +20 -0
- data/lib/tasks/migflow.rake +31 -0
- data/sig/migflow.rbs +3 -0
- 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
|
+
[](https://github.com/jv4lentim/migflow/actions/workflows/ci.yml)
|
|
8
|
+
[](./LICENSE.txt)
|
|
9
|
+
[](https://www.ruby-lang.org/)
|
|
10
|
+
[](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
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}
|