@crvouga/atlas 0.10.2 → 0.12.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 (114) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +7 -0
  3. package/dist/reports/src/catalog-health.d.ts +142 -0
  4. package/dist/reports/src/catalog-health.js +257 -0
  5. package/dist/reports/src/test-catalog.d.ts +8 -0
  6. package/dist/reports/src/test-catalog.js +5 -1
  7. package/dist/schema/src/api.d.ts +79 -0
  8. package/dist/schema/src/api.js +50 -0
  9. package/dist/server/src/app.js +9 -0
  10. package/dist/server/src/llms.js +1 -0
  11. package/dist/server/src/openapi.js +18 -2
  12. package/dist/server/src/settings-page.d.ts +4 -4
  13. package/dist/server/src/settings-page.js +17 -78
  14. package/docs/reference/http-api.md +34 -0
  15. package/docs/reference/reports.md +224 -0
  16. package/docs/reference/schema.md +10 -0
  17. package/docs/reference/server.md +4 -4
  18. package/docs/server.md +10 -0
  19. package/docs/ui.md +142 -0
  20. package/docs/workspaces.md +130 -0
  21. package/llms-full.txt +322 -0
  22. package/llms.txt +2 -0
  23. package/package.json +17 -10
  24. package/packages/schema/schemas/catalog-health.schema.json +400 -0
  25. package/packages/server/openapi.json +596 -0
  26. package/packages/visualizer/src/admin/SettingsPage.tsx +456 -0
  27. package/packages/visualizer/src/admin/main.tsx +11 -0
  28. package/packages/visualizer/src/app/AppShell.tsx +62 -38
  29. package/packages/visualizer/src/app/gazetteer.tsx +90 -83
  30. package/packages/visualizer/src/app/router.tsx +33 -15
  31. package/packages/visualizer/src/components/ActivityIndicator.tsx +4 -5
  32. package/packages/visualizer/src/components/DataIssues.tsx +36 -54
  33. package/packages/visualizer/src/components/Header.tsx +197 -125
  34. package/packages/visualizer/src/components/Legend.tsx +13 -41
  35. package/packages/visualizer/src/components/ProjectPicker.tsx +61 -26
  36. package/packages/visualizer/src/components/Screen.tsx +80 -71
  37. package/packages/visualizer/src/components/SpecLogo.tsx +15 -0
  38. package/packages/visualizer/src/components/Status.tsx +20 -34
  39. package/packages/visualizer/src/components/labels.tsx +8 -3
  40. package/packages/visualizer/src/components/spec-logos/APACHE-2.0.txt +206 -0
  41. package/packages/visualizer/src/components/spec-logos/CAMUNDA-MIT.txt +21 -0
  42. package/packages/visualizer/src/components/spec-logos/FILE-ICONS-ISC.txt +13 -0
  43. package/packages/visualizer/src/components/spec-logos/README.md +23 -0
  44. package/packages/visualizer/src/components/spec-logos/SIMPLE-ICONS-CC0.txt +30 -0
  45. package/packages/visualizer/src/components/spec-logos/alloy.svg +1 -0
  46. package/packages/visualizer/src/components/spec-logos/dmn.svg +3 -0
  47. package/packages/visualizer/src/components/spec-logos/gherkin.svg +1 -0
  48. package/packages/visualizer/src/components/spec-logos/openslo.svg +63 -0
  49. package/packages/visualizer/src/components/spec-logos/quint.svg +1 -0
  50. package/packages/visualizer/src/evidence/artifacts.tsx +263 -110
  51. package/packages/visualizer/src/evidence/chart-source.tsx +18 -0
  52. package/packages/visualizer/src/evidence/check-detail.tsx +283 -139
  53. package/packages/visualizer/src/evidence/decision-table.tsx +35 -26
  54. package/packages/visualizer/src/evidence/document-page.tsx +51 -29
  55. package/packages/visualizer/src/evidence/feature-page.tsx +121 -53
  56. package/packages/visualizer/src/evidence/marks.tsx +112 -79
  57. package/packages/visualizer/src/evidence/model-page.tsx +77 -46
  58. package/packages/visualizer/src/evidence/model-trace.tsx +55 -35
  59. package/packages/visualizer/src/evidence/overview.tsx +137 -69
  60. package/packages/visualizer/src/evidence/recording-page.tsx +70 -31
  61. package/packages/visualizer/src/evidence/recording.tsx +135 -68
  62. package/packages/visualizer/src/evidence/snapshot.tsx +99 -43
  63. package/packages/visualizer/src/evidence/source-text.tsx +33 -0
  64. package/packages/visualizer/src/evidence/sources-panel.tsx +138 -144
  65. package/packages/visualizer/src/evidence/spec-header.tsx +52 -36
  66. package/packages/visualizer/src/evidence/spec-index.tsx +61 -32
  67. package/packages/visualizer/src/evidence/spec-page.tsx +56 -25
  68. package/packages/visualizer/src/evidence/test-filter.tsx +38 -14
  69. package/packages/visualizer/src/explorer/Explorer.tsx +688 -261
  70. package/packages/visualizer/src/explorer/ExplorerIndex.tsx +136 -68
  71. package/packages/visualizer/src/explorer/RunsView.tsx +307 -94
  72. package/packages/visualizer/src/explorer/TestDetail.tsx +393 -118
  73. package/packages/visualizer/src/explorer/parts.tsx +35 -13
  74. package/packages/visualizer/src/find/Finder.tsx +106 -90
  75. package/packages/visualizer/src/find/open.ts +17 -20
  76. package/packages/visualizer/src/live/test-progress.tsx +60 -22
  77. package/packages/visualizer/src/looks/Stage.tsx +223 -110
  78. package/packages/visualizer/src/main.tsx +9 -11
  79. package/packages/visualizer/src/map/ChartMap.tsx +125 -69
  80. package/packages/visualizer/src/map/EventEdge.tsx +59 -37
  81. package/packages/visualizer/src/map/map.module.css +12 -546
  82. package/packages/visualizer/src/map/nodes.tsx +124 -70
  83. package/packages/visualizer/src/reachable.test.ts +3 -2
  84. package/packages/visualizer/src/spec-kind.ts +2 -2
  85. package/packages/visualizer/src/ui/grammars/ALLOY-MIT.txt +21 -0
  86. package/packages/visualizer/src/ui/grammars/QUINT-APACHE-2.0.txt +206 -0
  87. package/packages/visualizer/src/ui/grammars/README.md +14 -0
  88. package/packages/visualizer/src/ui/grammars/alloy.json +71 -0
  89. package/packages/visualizer/src/ui/grammars/quint.json +152 -0
  90. package/packages/visualizer/src/ui/highlight.ts +50 -0
  91. package/packages/visualizer/src/ui/theme.tsx +109 -0
  92. package/packages/visualizer/src/views/ChartPage.tsx +46 -26
  93. package/packages/visualizer/src/views/ChartTests.tsx +59 -22
  94. package/packages/visualizer/src/views/ProductPage.tsx +16 -15
  95. package/packages/visualizer/src/views/SidePanel.tsx +241 -131
  96. package/packages/visualizer/src/app/gazetteer.module.css +0 -107
  97. package/packages/visualizer/src/components/ActivityIndicator.module.css +0 -4
  98. package/packages/visualizer/src/components/DataIssues.module.css +0 -136
  99. package/packages/visualizer/src/components/Header.module.css +0 -114
  100. package/packages/visualizer/src/components/Legend.module.css +0 -84
  101. package/packages/visualizer/src/components/Screen.module.css +0 -312
  102. package/packages/visualizer/src/components/Status.module.css +0 -116
  103. package/packages/visualizer/src/components/icons.tsx +0 -156
  104. package/packages/visualizer/src/evidence/artifacts.module.css +0 -213
  105. package/packages/visualizer/src/evidence/evidence.module.css +0 -907
  106. package/packages/visualizer/src/evidence/recording.module.css +0 -460
  107. package/packages/visualizer/src/evidence/sources.module.css +0 -168
  108. package/packages/visualizer/src/evidence/spec-snapshot.tsx +0 -26
  109. package/packages/visualizer/src/explorer/explorer.module.css +0 -1238
  110. package/packages/visualizer/src/find/Finder.module.css +0 -168
  111. package/packages/visualizer/src/looks/stage.module.css +0 -300
  112. package/packages/visualizer/src/styles/global.css +0 -105
  113. package/packages/visualizer/src/styles/tokens.css +0 -80
  114. package/packages/visualizer/src/views/views.module.css +0 -546
@@ -1,460 +0,0 @@
1
- .recording {
2
- display: grid;
3
- gap: 14px;
4
- }
5
-
6
- .quietLine {
7
- margin: 0;
8
- color: var(--ink-3);
9
- font-size: 0.8125rem;
10
- }
11
-
12
- .link {
13
- padding: 0;
14
- border: 0;
15
- background: none;
16
- color: var(--place);
17
- font-weight: 500;
18
- cursor: pointer;
19
- }
20
-
21
- .error,
22
- .pre {
23
- margin: 0;
24
- padding: 10px 12px;
25
- border-radius: 8px;
26
- overflow: auto;
27
- max-height: 18rem;
28
- font-size: 0.75rem;
29
- line-height: 1.5;
30
- white-space: pre-wrap;
31
- overflow-wrap: anywhere;
32
- }
33
-
34
- .error {
35
- border-left: 3px solid var(--failed);
36
- background: var(--failed-soft);
37
- color: var(--failed-ink);
38
- font-family: var(--mono);
39
- }
40
-
41
- .error summary {
42
- margin-top: 6px;
43
- font-family: var(--font);
44
- font-weight: 600;
45
- cursor: pointer;
46
- }
47
-
48
- .pre {
49
- background: var(--bg-2);
50
- }
51
-
52
- .tabs {
53
- display: flex;
54
- gap: 4px;
55
- border-bottom: 1px solid var(--line);
56
- }
57
-
58
- .tabs button {
59
- padding: 8px 12px;
60
- border: 0;
61
- background: none;
62
- color: var(--ink-3);
63
- font-size: 0.875rem;
64
- font-weight: 500;
65
- cursor: pointer;
66
- }
67
-
68
- .tabs button[aria-selected='true'] {
69
- color: var(--ink);
70
- font-weight: 600;
71
- box-shadow: inset 0 -2px 0 var(--place);
72
- }
73
-
74
- .split {
75
- display: grid;
76
- grid-template-columns: minmax(15rem, 22rem) minmax(0, 1fr);
77
- gap: 28px;
78
- align-items: start;
79
- }
80
-
81
- .steps,
82
- .timing {
83
- list-style: none;
84
- margin: 0;
85
- padding: 0;
86
- }
87
-
88
- .steps {
89
- position: sticky;
90
- top: 0;
91
- max-height: calc(100vh - 13rem);
92
- overflow: auto;
93
- }
94
-
95
- .steps button,
96
- .timing button {
97
- display: flex;
98
- align-items: center;
99
- gap: 8px;
100
- width: 100%;
101
- padding: 6px 8px;
102
- border: 0;
103
- border-radius: 6px;
104
- background: none;
105
- font-size: 0.8125rem;
106
- text-align: left;
107
- cursor: pointer;
108
- }
109
-
110
- .steps button:hover:enabled,
111
- .timing button:hover:enabled {
112
- background: var(--bg-2);
113
- }
114
-
115
- /* A step that captured no moment has nothing to open. */
116
- .steps button:disabled,
117
- .timing button:disabled {
118
- color: var(--ink-3);
119
- cursor: default;
120
- }
121
-
122
- .steps button[data-status='failed'] {
123
- color: var(--failed-ink);
124
- font-weight: 600;
125
- }
126
-
127
- .steps button[aria-current='step'] {
128
- background: var(--place-soft);
129
- color: var(--ink);
130
- font-weight: 600;
131
- }
132
-
133
- .stepTitle {
134
- flex: 1;
135
- min-width: 0;
136
- overflow-wrap: anywhere;
137
- }
138
-
139
- .stepTime {
140
- flex: none;
141
- color: var(--faint);
142
- font-size: 0.75rem;
143
- font-weight: 400;
144
- font-variant-numeric: tabular-nums;
145
- }
146
-
147
- .stage {
148
- display: grid;
149
- gap: 12px;
150
- min-width: 0;
151
- }
152
-
153
- .transport {
154
- display: flex;
155
- flex-wrap: wrap;
156
- align-items: center;
157
- gap: 8px;
158
- }
159
-
160
- .control,
161
- .actor select {
162
- height: 2rem;
163
- padding: 0 12px;
164
- border: 1px solid var(--line-2);
165
- border-radius: var(--pill);
166
- background: var(--bg);
167
- font-size: 0.8125rem;
168
- font-weight: 600;
169
- cursor: pointer;
170
- }
171
-
172
- .control:hover:enabled {
173
- background: var(--bg-2);
174
- }
175
-
176
- .control:disabled {
177
- opacity: 0.45;
178
- cursor: default;
179
- }
180
-
181
- .scrub {
182
- flex: 1;
183
- min-width: 8rem;
184
- accent-color: var(--place);
185
- }
186
-
187
- .position {
188
- color: var(--ink-3);
189
- font-size: 0.8125rem;
190
- white-space: nowrap;
191
- font-variant-numeric: tabular-nums;
192
- }
193
-
194
- .momentTitle {
195
- margin: 6px 0 0;
196
- font-size: 1.125rem;
197
- font-weight: 600;
198
- line-height: 1.3;
199
- }
200
-
201
- .momentTitle[data-status='failed'] {
202
- color: var(--failed-ink);
203
- }
204
-
205
- .observed {
206
- padding: 10px 12px;
207
- border-radius: 8px;
208
- background: var(--bg-2);
209
- overflow: auto;
210
- font-size: 0.8125rem;
211
- }
212
-
213
- .pictures {
214
- display: flex;
215
- flex-wrap: wrap;
216
- align-content: flex-start;
217
- justify-content: center;
218
- gap: 20px;
219
- }
220
-
221
- .snapshot {
222
- flex: 0 0 270px;
223
- width: 270px;
224
- margin: 0;
225
- min-width: 0;
226
- }
227
-
228
- .snapshot[data-picture] { min-height: 14rem; }
229
-
230
- .snapshot:not([data-picture]) {
231
- padding: 10px 12px;
232
- border-radius: 8px;
233
- background: var(--bg-2);
234
- overflow: auto;
235
- font-size: 0.8125rem;
236
- }
237
-
238
- .snapshot figcaption {
239
- display: flex;
240
- flex-wrap: wrap;
241
- gap: 0 8px;
242
- margin-bottom: 6px;
243
- color: var(--ink-3);
244
- font-size: 0.8125rem;
245
- }
246
-
247
- .snapshot figcaption strong {
248
- color: var(--ink);
249
- }
250
-
251
- .earlier {
252
- color: var(--flaky-ink);
253
- }
254
-
255
- .phone {
256
- border: 1px solid var(--line-2);
257
- border-radius: 20px;
258
- overflow: hidden;
259
- background: #fff;
260
- }
261
-
262
-
263
- .image {
264
- display: block;
265
- width: 100%;
266
- height: auto;
267
- max-height: min(68vh, 640px);
268
- object-fit: contain;
269
- background: var(--bg-2);
270
- border: 1px solid var(--line-2);
271
- border-radius: 12px;
272
- }
273
-
274
- @media (max-width: 800px) {
275
- .snapshot { flex-basis: min(270px, 100%); width: min(270px, 100%); }
276
- }
277
-
278
- .data {
279
- margin: 0;
280
- }
281
-
282
- .data > div {
283
- display: grid;
284
- grid-template-columns: minmax(6rem, max-content) minmax(0, 1fr);
285
- gap: 0 12px;
286
- padding: 2px 0;
287
- }
288
-
289
- .data dt {
290
- color: var(--ink-3);
291
- }
292
-
293
- .data dd {
294
- margin: 0;
295
- min-width: 0;
296
- overflow-wrap: anywhere;
297
- }
298
-
299
- .data summary {
300
- color: var(--ink-3);
301
- cursor: pointer;
302
- }
303
-
304
- /* Timing */
305
-
306
- .timing li + li {
307
- border-top: 1px solid var(--line);
308
- }
309
-
310
- .timing button {
311
- border-radius: 0;
312
- }
313
-
314
- .bar {
315
- flex: none;
316
- width: 9rem;
317
- height: 6px;
318
- border-radius: var(--pill);
319
- background: var(--unrun);
320
- overflow: hidden;
321
- }
322
-
323
- .bar span {
324
- display: block;
325
- height: 100%;
326
- border-radius: var(--pill);
327
- background: var(--not-reached);
328
- }
329
-
330
- .timing li[data-band='warm'] .bar span {
331
- background: var(--ink-3);
332
- }
333
-
334
- .timing li[data-band='slow'] .bar span {
335
- background: var(--flaky);
336
- }
337
-
338
- .timing .stepTime {
339
- width: 6rem;
340
- text-align: right;
341
- }
342
-
343
- .slow {
344
- flex: none;
345
- padding: 0 6px;
346
- border-radius: 4px;
347
- background: var(--flaky-soft);
348
- color: var(--flaky-ink);
349
- font-size: 0.6875rem;
350
- font-weight: 600;
351
- }
352
-
353
- /* Model trace */
354
-
355
- .trace {
356
- display: grid;
357
- grid-template-columns: minmax(10rem, 16rem) minmax(0, 1fr);
358
- gap: 16px;
359
- align-items: start;
360
- }
361
-
362
- .traceStates {
363
- list-style: none;
364
- margin: 0;
365
- padding: 0;
366
- max-height: 24rem;
367
- overflow: auto;
368
- }
369
-
370
- .traceStates button {
371
- display: flex;
372
- gap: 8px;
373
- width: 100%;
374
- padding: 5px 8px;
375
- border: 0;
376
- border-radius: 6px;
377
- background: none;
378
- font-size: 0.8125rem;
379
- text-align: left;
380
- cursor: pointer;
381
- overflow-wrap: anywhere;
382
- }
383
-
384
- .traceStates button:hover {
385
- background: var(--bg-2);
386
- }
387
-
388
- .traceStates button[aria-pressed='true'] {
389
- background: var(--place-soft);
390
- font-weight: 600;
391
- }
392
-
393
- .traceNo {
394
- flex: none;
395
- width: 1.5rem;
396
- color: var(--ink-3);
397
- font-variant-numeric: tabular-nums;
398
- }
399
-
400
- .tableScroll {
401
- overflow-x: auto;
402
- }
403
-
404
- .traceTable {
405
- width: 100%;
406
- border-collapse: collapse;
407
- font-size: 0.8125rem;
408
- }
409
-
410
- .traceTable th,
411
- .traceTable td {
412
- padding: 6px 10px;
413
- border-bottom: 1px solid var(--line);
414
- text-align: left;
415
- vertical-align: top;
416
- }
417
-
418
- .traceTable thead th {
419
- color: var(--ink-3);
420
- font-size: 0.75rem;
421
- font-weight: 600;
422
- }
423
-
424
- .traceTable tbody th {
425
- font-weight: 500;
426
- white-space: nowrap;
427
- }
428
-
429
- .traceTable pre {
430
- margin: 0;
431
- white-space: pre-wrap;
432
- overflow-wrap: anywhere;
433
- }
434
-
435
- .traceTable tr[data-changed] td:last-child {
436
- background: var(--place-soft);
437
- }
438
-
439
- .changed {
440
- display: block;
441
- color: var(--place);
442
- font-size: 0.6875rem;
443
- font-weight: 600;
444
- }
445
-
446
- @media (max-width: 800px) {
447
- .split,
448
- .trace {
449
- grid-template-columns: 1fr;
450
- }
451
-
452
- .steps {
453
- position: static;
454
- max-height: 16rem;
455
- }
456
-
457
- .bar {
458
- width: 4rem;
459
- }
460
- }
@@ -1,168 +0,0 @@
1
- .overlay {
2
- position: fixed;
3
- inset: 0;
4
- z-index: 60;
5
- background: rgba(15, 20, 25, 0.25);
6
- }
7
-
8
- .sheet {
9
- position: fixed;
10
- top: 0;
11
- right: 0;
12
- bottom: 0;
13
- z-index: 61;
14
- width: min(30rem, 100vw);
15
- padding: 18px 20px 40px;
16
- overflow: auto;
17
- background: var(--bg);
18
- box-shadow: var(--shadow-pop);
19
- }
20
-
21
- .head {
22
- display: flex;
23
- justify-content: space-between;
24
- align-items: center;
25
- }
26
-
27
- .title {
28
- margin: 0;
29
- font-size: 1.125rem;
30
- font-weight: 650;
31
- }
32
-
33
- .close {
34
- display: inline-flex;
35
- padding: 8px;
36
- border: 0;
37
- border-radius: 50%;
38
- background: none;
39
- color: var(--ink-3);
40
- cursor: pointer;
41
- }
42
-
43
- .close:hover {
44
- background: var(--bg-2);
45
- }
46
-
47
- .lead {
48
- margin: 8px 0 12px;
49
- color: var(--ink-3);
50
- font-size: 0.875rem;
51
- }
52
-
53
- .reset {
54
- padding: 5px 12px;
55
- border: 1px solid var(--place);
56
- border-radius: var(--pill);
57
- background: var(--bg);
58
- color: var(--place);
59
- font-size: 0.8125rem;
60
- font-weight: 600;
61
- cursor: pointer;
62
- }
63
-
64
- .notice {
65
- margin: 12px 0 0;
66
- padding: 10px 12px;
67
- border-radius: 8px;
68
- background: var(--bg-2);
69
- font-size: 0.8125rem;
70
- }
71
-
72
- .link,
73
- .only {
74
- padding: 0;
75
- border: 0;
76
- background: none;
77
- color: var(--place);
78
- font-size: 0.8125rem;
79
- font-weight: 500;
80
- cursor: pointer;
81
- }
82
-
83
- .group {
84
- margin-top: 22px;
85
- }
86
-
87
- .group h3 {
88
- margin: 0 0 4px;
89
- color: var(--ink-3);
90
- font-size: 0.75rem;
91
- font-weight: 600;
92
- }
93
-
94
- .group ul {
95
- list-style: none;
96
- margin: 0;
97
- padding: 0;
98
- }
99
-
100
- .group li {
101
- display: flex;
102
- align-items: center;
103
- gap: 10px;
104
- border-bottom: 1px solid var(--line);
105
- }
106
-
107
- .group li > button:not(.only) {
108
- padding: 8px 0;
109
- border: 0;
110
- background: none;
111
- color: var(--place);
112
- font-size: 0.8125rem;
113
- cursor: pointer;
114
- }
115
-
116
- .empty {
117
- margin: 4px 0;
118
- color: var(--ink-3);
119
- font-size: 0.875rem;
120
- }
121
-
122
- .option {
123
- display: flex;
124
- flex: 1;
125
- align-items: center;
126
- gap: 10px;
127
- min-width: 0;
128
- padding: 8px 0;
129
- font-size: 0.875rem;
130
- cursor: pointer;
131
- }
132
-
133
- .option input {
134
- flex: none;
135
- width: 1rem;
136
- height: 1rem;
137
- accent-color: var(--place);
138
- }
139
-
140
- .option[data-off] {
141
- color: var(--faint);
142
- cursor: default;
143
- }
144
-
145
- .optionName {
146
- flex: 1;
147
- min-width: 0;
148
- font-weight: 500;
149
- overflow-wrap: anywhere;
150
- }
151
-
152
- .run {
153
- display: block;
154
- color: var(--ink-3);
155
- font-size: 0.75rem;
156
- font-weight: 400;
157
- }
158
-
159
- .optionMeta {
160
- flex: none;
161
- color: var(--ink-3);
162
- font-size: 0.75rem;
163
- text-align: right;
164
- }
165
-
166
- .optionMeta[data-failed] {
167
- color: var(--failed);
168
- }
@@ -1,26 +0,0 @@
1
- import styles from './evidence.module.css';
2
-
3
- /** A fixed-size visual summary made from the spec itself, available before any run exists. */
4
- export function SpecSnapshot({ title, text, kind }: { title: string; text: string; kind: string }) {
5
- const lines = text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).slice(0, 4);
6
- return (
7
- <figure className={styles.specSnapshot}>
8
- <figcaption>Spec snapshot <span>Generated from {kind} source</span></figcaption>
9
- <svg className={styles.specSnapshotImage} viewBox="0 0 960 260" role="img" aria-label={`Visual summary of ${title}`}>
10
- <rect width="960" height="260" rx="18" />
11
- <text className={styles.snapshotTitle} x="32" y="52">{title}</text>
12
- {lines.map((line, index) => {
13
- const y = 90 + index * 40;
14
- return (
15
- <g key={`${index}-${line}`}>
16
- <circle cx="42" cy={y - 5} r="5" />
17
- <rect className={styles.snapshotLine} x="60" y={y - 15} width="868" height="28" rx="7" />
18
- <text className={styles.snapshotText} x="76" y={y + 4}>{line.slice(0, 112)}{line.length > 112 ? '…' : ''}</text>
19
- </g>
20
- );
21
- })}
22
- {lines.length === 0 && <text className={styles.snapshotText} x="32" y="112">No readable source lines</text>}
23
- </svg>
24
- </figure>
25
- );
26
- }