regresso 0.1.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 (79) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +3 -0
  3. data/CHANGELOG.md +3 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +258 -0
  6. data/Rakefile +12 -0
  7. data/assets/logo-header.svg +28 -0
  8. data/lib/generators/regresso/install_generator.rb +29 -0
  9. data/lib/generators/regresso/templates/regresso.rb +18 -0
  10. data/lib/regresso/adapters/.gitkeep +0 -0
  11. data/lib/regresso/adapters/base.rb +30 -0
  12. data/lib/regresso/adapters/csv.rb +43 -0
  13. data/lib/regresso/adapters/database.rb +53 -0
  14. data/lib/regresso/adapters/database_snapshot.rb +49 -0
  15. data/lib/regresso/adapters/graphql.rb +75 -0
  16. data/lib/regresso/adapters/graphql_batch.rb +42 -0
  17. data/lib/regresso/adapters/http.rb +70 -0
  18. data/lib/regresso/adapters/json_file.rb +30 -0
  19. data/lib/regresso/adapters/proc.rb +30 -0
  20. data/lib/regresso/ci/github_annotation_formatter.rb +46 -0
  21. data/lib/regresso/ci/github_pr_commenter.rb +55 -0
  22. data/lib/regresso/ci/junit_xml_formatter.rb +42 -0
  23. data/lib/regresso/ci/reporter.rb +49 -0
  24. data/lib/regresso/ci.rb +6 -0
  25. data/lib/regresso/comparator.rb +45 -0
  26. data/lib/regresso/configuration.rb +92 -0
  27. data/lib/regresso/differ.rb +129 -0
  28. data/lib/regresso/difference.rb +45 -0
  29. data/lib/regresso/history/entry.rb +70 -0
  30. data/lib/regresso/history/file_backend.rb +51 -0
  31. data/lib/regresso/history/statistics.rb +65 -0
  32. data/lib/regresso/history/store.rb +122 -0
  33. data/lib/regresso/history/trend_reporter.rb +68 -0
  34. data/lib/regresso/history.rb +7 -0
  35. data/lib/regresso/json_path.rb +55 -0
  36. data/lib/regresso/minitest.rb +107 -0
  37. data/lib/regresso/notifiers/base.rb +33 -0
  38. data/lib/regresso/notifiers/microsoft_teams.rb +60 -0
  39. data/lib/regresso/notifiers/slack.rb +72 -0
  40. data/lib/regresso/notifiers.rb +5 -0
  41. data/lib/regresso/parallel/comparison_result.rb +51 -0
  42. data/lib/regresso/parallel/parallel_result.rb +68 -0
  43. data/lib/regresso/parallel/result_aggregator.rb +25 -0
  44. data/lib/regresso/parallel/runner.rb +71 -0
  45. data/lib/regresso/parallel.rb +6 -0
  46. data/lib/regresso/reporter.rb +68 -0
  47. data/lib/regresso/result.rb +70 -0
  48. data/lib/regresso/rspec/.gitkeep +0 -0
  49. data/lib/regresso/rspec/shared_examples.rb +42 -0
  50. data/lib/regresso/rspec.rb +142 -0
  51. data/lib/regresso/snapshot_manager.rb +57 -0
  52. data/lib/regresso/tasks/ci.rake +51 -0
  53. data/lib/regresso/tasks/history.rake +36 -0
  54. data/lib/regresso/templates/.gitkeep +0 -0
  55. data/lib/regresso/templates/report.html.erb +44 -0
  56. data/lib/regresso/version.rb +6 -0
  57. data/lib/regresso/web_ui/diff_formatter.rb +46 -0
  58. data/lib/regresso/web_ui/public/css/app.css +239 -0
  59. data/lib/regresso/web_ui/public/js/app.js +117 -0
  60. data/lib/regresso/web_ui/result_store.rb +60 -0
  61. data/lib/regresso/web_ui/server.rb +70 -0
  62. data/lib/regresso/web_ui/views/index.erb +58 -0
  63. data/lib/regresso/web_ui/views/layout.erb +13 -0
  64. data/lib/regresso/web_ui.rb +5 -0
  65. data/lib/regresso.rb +46 -0
  66. data/sig/regresso.rbs +4 -0
  67. data/site/Gemfile +7 -0
  68. data/site/assets/logo-header.png +0 -0
  69. data/site/assets/site.css +458 -0
  70. data/site/content/index.md +6 -0
  71. data/site/craze.yml +24 -0
  72. data/site/dist/assets/logo-header.svg +28 -0
  73. data/site/dist/assets/site.css +458 -0
  74. data/site/dist/index.html +232 -0
  75. data/site/dist/search.json +9 -0
  76. data/site/dist/sitemap.xml +7 -0
  77. data/site/templates/index.erb +232 -0
  78. data/site/templates/layout.erb +17 -0
  79. metadata +190 -0
@@ -0,0 +1,458 @@
1
+ :root {
2
+ --bg: #0b0f14;
3
+ --bg-accent: #111826;
4
+ --ink: #eef2f8;
5
+ --muted: #9aa6b2;
6
+ --accent: #ff6a3d;
7
+ --accent-2: #3bd6c6;
8
+ --accent-3: #f2b84b;
9
+ --card: #121923;
10
+ --card-2: #0f1622;
11
+ --stroke: rgba(255, 255, 255, 0.08);
12
+ --shadow: 0 28px 70px rgba(2, 8, 18, 0.6);
13
+ }
14
+
15
+ * {
16
+ box-sizing: border-box;
17
+ margin: 0;
18
+ padding: 0;
19
+ }
20
+
21
+ body {
22
+ font-family: "Space Grotesk", "Work Sans", sans-serif;
23
+ background: radial-gradient(circle at 15% 10%, rgba(59, 214, 198, 0.16), transparent 45%),
24
+ radial-gradient(circle at 85% 5%, rgba(255, 106, 61, 0.14), transparent 40%),
25
+ radial-gradient(circle at 30% 80%, rgba(242, 184, 75, 0.14), transparent 45%),
26
+ linear-gradient(160deg, #0b0f14 10%, #0a111c 55%, #0c141f 100%);
27
+ color: var(--ink);
28
+ line-height: 1.6;
29
+ }
30
+
31
+ .ambient-orbit {
32
+ position: fixed;
33
+ inset: -20vh -10vw auto auto;
34
+ width: 50vw;
35
+ height: 50vw;
36
+ background: radial-gradient(circle at 30% 30%, rgba(255, 106, 61, 0.3), transparent 65%),
37
+ radial-gradient(circle at 70% 40%, rgba(59, 214, 198, 0.24), transparent 60%);
38
+ filter: blur(12px);
39
+ pointer-events: none;
40
+ animation: drift 18s ease-in-out infinite;
41
+ z-index: 0;
42
+ }
43
+
44
+ @keyframes drift {
45
+ 0%,
46
+ 100% {
47
+ transform: translateY(0) rotate(0deg);
48
+ }
49
+ 50% {
50
+ transform: translateY(-20px) rotate(3deg);
51
+ }
52
+ }
53
+
54
+ .hero {
55
+ padding: 48px 8vw 80px;
56
+ position: relative;
57
+ z-index: 1;
58
+ }
59
+
60
+ .nav {
61
+ display: flex;
62
+ align-items: center;
63
+ justify-content: space-between;
64
+ gap: 24px;
65
+ margin-bottom: 64px;
66
+ }
67
+
68
+ .nav nav {
69
+ display: flex;
70
+ flex-wrap: wrap;
71
+ gap: 16px;
72
+ align-items: center;
73
+ }
74
+
75
+ .nav a {
76
+ text-decoration: none;
77
+ color: var(--muted);
78
+ font-weight: 500;
79
+ }
80
+
81
+ .nav a:hover {
82
+ color: var(--ink);
83
+ }
84
+
85
+ .logo {
86
+ display: flex;
87
+ align-items: center;
88
+ gap: 12px;
89
+ font-weight: 700;
90
+ font-size: 1.1rem;
91
+ }
92
+
93
+ .logo img {
94
+ width: 42px;
95
+ height: 42px;
96
+ }
97
+
98
+ .hero-grid {
99
+ display: grid;
100
+ grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr);
101
+ gap: 48px;
102
+ align-items: center;
103
+ }
104
+
105
+ .eyebrow {
106
+ text-transform: uppercase;
107
+ letter-spacing: 0.14em;
108
+ font-size: 0.8rem;
109
+ color: var(--accent-2);
110
+ font-weight: 600;
111
+ margin-bottom: 16px;
112
+ }
113
+
114
+ .hero h1 {
115
+ font-size: clamp(2.6rem, 4vw, 4rem);
116
+ line-height: 1.1;
117
+ margin-bottom: 20px;
118
+ }
119
+
120
+ .lead {
121
+ font-size: 1.1rem;
122
+ color: var(--muted);
123
+ margin-bottom: 28px;
124
+ }
125
+
126
+ .cta {
127
+ display: flex;
128
+ gap: 16px;
129
+ flex-wrap: wrap;
130
+ }
131
+
132
+ .button {
133
+ border-radius: 999px;
134
+ padding: 12px 24px;
135
+ border: 1px solid var(--stroke);
136
+ text-decoration: none;
137
+ font-weight: 600;
138
+ color: var(--ink);
139
+ background: var(--card);
140
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
141
+ }
142
+
143
+ .button.primary {
144
+ background: linear-gradient(120deg, #ff6a3d, #ff9c6a);
145
+ color: #091118;
146
+ border: none;
147
+ box-shadow: 0 14px 40px rgba(255, 106, 61, 0.35);
148
+ }
149
+
150
+ .button.ghost {
151
+ background: transparent;
152
+ }
153
+
154
+ .button:hover {
155
+ transform: translateY(-2px);
156
+ box-shadow: 0 16px 32px rgba(2, 8, 18, 0.45);
157
+ }
158
+
159
+ .signal {
160
+ margin-top: 24px;
161
+ display: flex;
162
+ flex-wrap: wrap;
163
+ gap: 12px;
164
+ color: var(--muted);
165
+ font-size: 0.9rem;
166
+ }
167
+
168
+ .signal span {
169
+ background: rgba(18, 25, 35, 0.8);
170
+ border: 1px solid var(--stroke);
171
+ padding: 6px 12px;
172
+ border-radius: 999px;
173
+ }
174
+
175
+ .hero-card {
176
+ background: var(--card);
177
+ border: 1px solid var(--stroke);
178
+ border-radius: 24px;
179
+ padding: 24px;
180
+ box-shadow: var(--shadow);
181
+ position: relative;
182
+ overflow: hidden;
183
+ }
184
+
185
+ .hero-card::after {
186
+ content: "";
187
+ position: absolute;
188
+ inset: auto -40% -40% auto;
189
+ width: 200px;
190
+ height: 200px;
191
+ background: radial-gradient(circle, rgba(59, 214, 198, 0.25), transparent 70%);
192
+ z-index: 0;
193
+ }
194
+
195
+ .card-header,
196
+ .card-footer {
197
+ display: flex;
198
+ align-items: center;
199
+ justify-content: space-between;
200
+ gap: 16px;
201
+ }
202
+
203
+ .card-header {
204
+ margin-bottom: 16px;
205
+ font-size: 0.9rem;
206
+ color: var(--muted);
207
+ }
208
+
209
+ .status {
210
+ padding: 4px 12px;
211
+ border-radius: 999px;
212
+ font-weight: 600;
213
+ font-size: 0.8rem;
214
+ }
215
+
216
+ .status.ok {
217
+ background: rgba(59, 214, 198, 0.18);
218
+ color: #b9fff4;
219
+ }
220
+
221
+ .card-body {
222
+ background: #0b0f14;
223
+ border-radius: 16px;
224
+ padding: 16px;
225
+ color: #fff;
226
+ margin-bottom: 20px;
227
+ position: relative;
228
+ z-index: 1;
229
+ }
230
+
231
+ pre {
232
+ font-family: "IBM Plex Mono", "SFMono-Regular", monospace;
233
+ font-size: 0.85rem;
234
+ white-space: pre-wrap;
235
+ }
236
+
237
+ .card-footer .label {
238
+ font-size: 0.75rem;
239
+ color: var(--muted);
240
+ }
241
+
242
+ .card-footer .value {
243
+ font-size: 1.4rem;
244
+ font-weight: 700;
245
+ }
246
+
247
+ .section {
248
+ padding: 80px 8vw;
249
+ position: relative;
250
+ z-index: 1;
251
+ }
252
+
253
+ .section-title {
254
+ max-width: 640px;
255
+ margin-bottom: 48px;
256
+ }
257
+
258
+ .section h2 {
259
+ font-size: clamp(2rem, 3vw, 3rem);
260
+ margin-bottom: 16px;
261
+ }
262
+
263
+ .section p {
264
+ color: var(--muted);
265
+ }
266
+
267
+ .feature-grid,
268
+ .adapter-grid {
269
+ display: grid;
270
+ gap: 20px;
271
+ }
272
+
273
+ .feature-grid {
274
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
275
+ }
276
+
277
+ .feature {
278
+ background: var(--card);
279
+ border: 1px solid var(--stroke);
280
+ border-radius: 20px;
281
+ padding: 20px;
282
+ box-shadow: 0 14px 34px rgba(2, 8, 18, 0.5);
283
+ }
284
+
285
+ .feature h3 {
286
+ margin-bottom: 10px;
287
+ }
288
+
289
+ .split {
290
+ display: grid;
291
+ grid-template-columns: minmax(0, 1.2fr) minmax(0, 0.8fr);
292
+ gap: 40px;
293
+ align-items: start;
294
+ }
295
+
296
+ .callouts {
297
+ display: grid;
298
+ gap: 16px;
299
+ margin-top: 24px;
300
+ }
301
+
302
+ .note {
303
+ background: linear-gradient(180deg, #121b27, #0f151f);
304
+ border: 1px solid var(--stroke);
305
+ border-radius: 24px;
306
+ padding: 24px;
307
+ box-shadow: 0 18px 38px rgba(2, 8, 18, 0.6);
308
+ }
309
+
310
+ .note h4 {
311
+ margin-bottom: 12px;
312
+ }
313
+
314
+ .small {
315
+ font-size: 0.85rem;
316
+ color: var(--muted);
317
+ margin-top: 12px;
318
+ }
319
+
320
+ .adapter-grid {
321
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
322
+ }
323
+
324
+ .adapter {
325
+ padding: 16px 18px;
326
+ border-radius: 16px;
327
+ border: 1px dashed var(--stroke);
328
+ background: rgba(18, 25, 35, 0.7);
329
+ font-weight: 600;
330
+ }
331
+
332
+ .steps {
333
+ display: grid;
334
+ gap: 12px;
335
+ list-style: none;
336
+ counter-reset: step;
337
+ }
338
+
339
+ .steps li {
340
+ background: var(--card);
341
+ border: 1px solid var(--stroke);
342
+ border-radius: 16px;
343
+ padding: 16px 20px;
344
+ counter-increment: step;
345
+ }
346
+
347
+ .steps li::before {
348
+ content: counter(step);
349
+ display: inline-flex;
350
+ align-items: center;
351
+ justify-content: center;
352
+ width: 32px;
353
+ height: 32px;
354
+ border-radius: 50%;
355
+ background: var(--accent-3);
356
+ color: #1a1f2b;
357
+ font-weight: 700;
358
+ margin-right: 12px;
359
+ }
360
+
361
+ .ui-preview {
362
+ background: #0f151f;
363
+ color: #fff;
364
+ border-radius: 24px;
365
+ padding: 20px;
366
+ box-shadow: var(--shadow);
367
+ }
368
+
369
+ .ui-header {
370
+ display: flex;
371
+ justify-content: space-between;
372
+ margin-bottom: 16px;
373
+ font-size: 0.9rem;
374
+ }
375
+
376
+ .ui-body {
377
+ display: grid;
378
+ gap: 12px;
379
+ }
380
+
381
+ .ui-row {
382
+ display: grid;
383
+ grid-template-columns: 1fr 0.6fr 0.6fr auto;
384
+ gap: 12px;
385
+ background: rgba(255, 255, 255, 0.06);
386
+ padding: 10px 12px;
387
+ border-radius: 12px;
388
+ font-size: 0.85rem;
389
+ }
390
+
391
+ .pill {
392
+ padding: 4px 10px;
393
+ border-radius: 999px;
394
+ font-size: 0.75rem;
395
+ }
396
+
397
+ .pill.ok {
398
+ background: rgba(59, 214, 198, 0.24);
399
+ color: #c7fff7;
400
+ }
401
+
402
+ .pill.warn {
403
+ background: rgba(242, 184, 75, 0.24);
404
+ color: #ffe0a6;
405
+ }
406
+
407
+ .footer {
408
+ background: var(--card);
409
+ border-radius: 32px;
410
+ margin: 40px 8vw 80px;
411
+ padding: 40px;
412
+ display: flex;
413
+ justify-content: space-between;
414
+ align-items: center;
415
+ box-shadow: var(--shadow);
416
+ }
417
+
418
+ @media (max-width: 960px) {
419
+ .hero-grid,
420
+ .split {
421
+ grid-template-columns: 1fr;
422
+ }
423
+
424
+ .nav {
425
+ flex-direction: column;
426
+ align-items: flex-start;
427
+ }
428
+
429
+ .footer {
430
+ flex-direction: column;
431
+ gap: 20px;
432
+ text-align: left;
433
+ }
434
+ }
435
+
436
+ @media (max-width: 640px) {
437
+ .hero {
438
+ padding: 32px 6vw 60px;
439
+ }
440
+
441
+ .section {
442
+ padding: 60px 6vw;
443
+ }
444
+
445
+ .ui-row {
446
+ grid-template-columns: 1fr;
447
+ }
448
+ }
449
+
450
+ @media (prefers-reduced-motion: reduce) {
451
+ .ambient-orbit {
452
+ animation: none;
453
+ }
454
+
455
+ .button {
456
+ transition: none;
457
+ }
458
+ }
@@ -0,0 +1,232 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Regresso - Regression testing for Ruby</title>
7
+ <meta name="description" content="Regression testing for Ruby APIs, files, and data snapshots.">
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Work+Sans:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
11
+ <link rel="stylesheet" href="/regresso/assets/site.css">
12
+ </head>
13
+ <body>
14
+ <div class="ambient-orbit"></div>
15
+
16
+ <section class="hero">
17
+ <header class="nav">
18
+ <div class="logo">
19
+ <img src="/regresso/assets/logo-header.png" alt="Regresso logo">
20
+ <span>Regresso</span>
21
+ </div>
22
+ <nav>
23
+ <a href="#features">Features</a>
24
+ <a href="#adapters">Adapters</a>
25
+ <a href="#how">How It Works</a>
26
+ <a href="#web-ui">Web UI</a>
27
+ <a href="#quick-start" class="button ghost">Quick Start</a>
28
+ </nav>
29
+ </header>
30
+
31
+ <div class="hero-grid">
32
+ <div class="hero-copy">
33
+ <p class="eyebrow">Regression testing for Ruby data</p>
34
+ <h1>Catch production drift across APIs, files, and databases.</h1>
35
+ <p class="lead">Regresso compares structured outputs with precision tolerances, ignore rules, and snapshot workflows. Ship confidently, even when data changes are subtle.</p>
36
+ <div class="cta">
37
+ <a class="button primary" href="#quick-start">Get started</a>
38
+ <a class="button ghost" href="https://github.com/ydah/regresso">GitHub</a>
39
+ </div>
40
+ <div class="signal">
41
+ <span>Ruby 3.2+</span>
42
+ <span>APIs · CSV · DB · GraphQL</span>
43
+ <span>CI-ready reports</span>
44
+ </div>
45
+ </div>
46
+
47
+ <div class="hero-card">
48
+ <div class="card-header">
49
+ <span>Latest comparison</span>
50
+ <span class="status ok">Passed</span>
51
+ </div>
52
+ <div class="card-body">
53
+ <pre><code>expect(new_api)
54
+ .to have_no_regression_from(old_api)
55
+ .with_tolerance(0.01)
56
+ .ignoring("$.timestamp")</code></pre>
57
+ </div>
58
+ <div class="card-footer">
59
+ <div>
60
+ <p class="label">Meaningful diffs</p>
61
+ <p class="value">0</p>
62
+ </div>
63
+ <div>
64
+ <p class="label">Runtime</p>
65
+ <p class="value">1.2s</p>
66
+ </div>
67
+ </div>
68
+ </div>
69
+ </div>
70
+ </section>
71
+
72
+ <section id="features" class="section">
73
+ <div class="section-title">
74
+ <h2>Designed for regression certainty</h2>
75
+ <p>Pinpoint where outputs drift and decide what matters to your product.</p>
76
+ </div>
77
+ <div class="feature-grid">
78
+ <article class="feature">
79
+ <h3>Precision diffs</h3>
80
+ <p>JSONPath-level ignores and numeric tolerances keep the signal clean.</p>
81
+ </article>
82
+ <article class="feature">
83
+ <h3>Source adapters</h3>
84
+ <p>Compare HTTP, CSV, JSON files, GraphQL, database queries, and snapshots.</p>
85
+ </article>
86
+ <article class="feature">
87
+ <h3>Snapshots</h3>
88
+ <p>Freeze stable output and review changes when business logic evolves.</p>
89
+ </article>
90
+ <article class="feature">
91
+ <h3>Parallel runs</h3>
92
+ <p>Run many comparisons concurrently and aggregate CI-ready reports.</p>
93
+ </article>
94
+ <article class="feature">
95
+ <h3>Test frameworks</h3>
96
+ <p>RSpec matchers and Minitest assertions come out of the box.</p>
97
+ </article>
98
+ <article class="feature">
99
+ <h3>Web UI</h3>
100
+ <p>Inspect stored results, filter diffs, and share reports with your team.</p>
101
+ </article>
102
+ </div>
103
+ </section>
104
+
105
+ </body>
106
+ </html>
107
+
108
+ <section id="quick-start" class="section split">
109
+ <div>
110
+ <h2>Quick start</h2>
111
+ <p>Install the gem, compare two sources, and inspect the summary.</p>
112
+ <div class="callouts">
113
+ <div>
114
+ <h4>Install</h4>
115
+ <pre><code>bundle add regresso</code></pre>
116
+ </div>
117
+ <div>
118
+ <h4>Compare</h4>
119
+ <pre><code>result = Regresso::Comparator.new(
120
+ source_a: old_api,
121
+ source_b: new_api
122
+ ).compare
123
+ puts result.summary</code></pre>
124
+ </div>
125
+ </div>
126
+ </div>
127
+ <aside class="note">
128
+ <h4>Snapshot flow</h4>
129
+ <p>Lock in stable outputs and keep regressions intentional.</p>
130
+ <pre><code>expect(payload)
131
+ .to match_snapshot("report_total")</code></pre>
132
+ <p class="small">Set <code>UPDATE_SNAPSHOTS=1</code> to refresh stored snapshots.</p>
133
+ </aside>
134
+ </section>
135
+
136
+ <section id="adapters" class="section">
137
+ <div class="section-title">
138
+ <h2>Adapters you can chain together</h2>
139
+ <p>Everything is just a source that responds to <code>fetch</code>.</p>
140
+ </div>
141
+ <div class="adapter-grid">
142
+ <div class="adapter">HTTP APIs</div>
143
+ <div class="adapter">CSV exports</div>
144
+ <div class="adapter">JSON files</div>
145
+ <div class="adapter">GraphQL queries</div>
146
+ <div class="adapter">GraphQL batches</div>
147
+ <div class="adapter">Database queries</div>
148
+ <div class="adapter">Database snapshots</div>
149
+ <div class="adapter">Custom Proc adapters</div>
150
+ </div>
151
+ </section>
152
+
153
+ <section id="how" class="section">
154
+ <div class="section-title">
155
+ <h2>How it works</h2>
156
+ <p>Regresso compares structured data with a clear audit trail.</p>
157
+ </div>
158
+ <ol class="steps">
159
+ <li>Normalize inputs from two sources.</li>
160
+ <li>Compute diffs with type coercion and tolerance rules.</li>
161
+ <li>Filter out ignored paths and noise.</li>
162
+ <li>Generate reports for CI or the Web UI.</li>
163
+ </ol>
164
+ </section>
165
+
166
+ <section id="web-ui" class="section split">
167
+ <div>
168
+ <h2>Web UI for shared visibility</h2>
169
+ <p>Store results on disk, filter diffs, and keep a browseable trail of comparisons.</p>
170
+ <pre><code>require "regresso/web_ui"
171
+
172
+ store = Regresso::WebUI::ResultStore.new(
173
+ storage_path: "tmp/regresso_results"
174
+ )
175
+ Regresso::WebUI::Server.set :result_store, store
176
+ Regresso::WebUI::Server.run!</code></pre>
177
+ </div>
178
+ <div class="ui-preview">
179
+ <div class="ui-header">
180
+ <span>Run #128</span>
181
+ <span class="pill">Diffs</span>
182
+ </div>
183
+ <div class="ui-body">
184
+ <div class="ui-row">
185
+ <span>$.prices[2]</span>
186
+ <span>19.99</span>
187
+ <span>20.10</span>
188
+ <span class="pill warn">changed</span>
189
+ </div>
190
+ <div class="ui-row">
191
+ <span>$.users[4]</span>
192
+ <span>nil</span>
193
+ <span>"Nia"</span>
194
+ <span class="pill ok">added</span>
195
+ </div>
196
+ </div>
197
+ </div>
198
+ </section>
199
+
200
+ <section class="section split">
201
+ <div>
202
+ <h2>CI-friendly outputs</h2>
203
+ <p>Generate JUnit XML or GitHub annotations from parallel runs.</p>
204
+ <pre><code>runner = Regresso::Parallel::Runner.new(
205
+ comparisons: comparisons,
206
+ workers: 4
207
+ )
208
+ result = runner.run
209
+
210
+ reporter = Regresso::CI::Reporter.new(result)
211
+ File.write("tmp/regresso.xml", reporter.to_junit_xml)</code></pre>
212
+ </div>
213
+ <div class="note">
214
+ <h4>History tracking</h4>
215
+ <p>Persist results and build weekly trend reports.</p>
216
+ <pre><code>store = Regresso::History::Store.new
217
+ store.record(result)
218
+ report = Regresso::History::TrendReporter.new(store)
219
+ puts report.generate(period: :week)</code></pre>
220
+ </div>
221
+ </section>
222
+
223
+ <section class="section footer">
224
+ <div>
225
+ <h2>Ready to stop regressions before they ship?</h2>
226
+ <p>Wire Regresso into your test suite and keep data drift visible.</p>
227
+ </div>
228
+ <div class="cta">
229
+ <a class="button primary" href="#quick-start">Start now</a>
230
+ <a class="button ghost" href="https://rubygems.org/gems/regresso">RubyGems</a>
231
+ </div>
232
+ </section>
@@ -0,0 +1,9 @@
1
+ [
2
+ {
3
+ "title": "Regresso",
4
+ "url": "/",
5
+ "content": "Regresso is a regression testing toolkit for Ruby APIs, files, and data snapshots.",
6
+ "tags": [],
7
+ "date": null
8
+ }
9
+ ]
@@ -0,0 +1,7 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
3
+ <url>
4
+ <loc>https://ydah.github.io/regresso/</loc>
5
+ <lastmod>2026-01-18</lastmod>
6
+ </url>
7
+ </urlset>