rubyllm-observ 0.5.1 → 0.6.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 (95) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +54 -6
  3. data/app/assets/stylesheets/observ/_annotations.scss +114 -103
  4. data/app/assets/stylesheets/observ/_card.scss +58 -49
  5. data/app/assets/stylesheets/observ/_chat.scss +247 -155
  6. data/app/assets/stylesheets/observ/_components.scss +622 -340
  7. data/app/assets/stylesheets/observ/_dashboard.scss +31 -28
  8. data/app/assets/stylesheets/observ/_datasets.scss +494 -547
  9. data/app/assets/stylesheets/observ/_drawer.scss +250 -228
  10. data/app/assets/stylesheets/observ/_filters.scss +139 -0
  11. data/app/assets/stylesheets/observ/_json_viewer.scss +103 -97
  12. data/app/assets/stylesheets/observ/_layout.scss +443 -178
  13. data/app/assets/stylesheets/observ/_metrics.scss +79 -76
  14. data/app/assets/stylesheets/observ/_namespace.scss +18 -0
  15. data/app/assets/stylesheets/observ/_observations.scss +122 -119
  16. data/app/assets/stylesheets/observ/_pagination.scss +129 -112
  17. data/app/assets/stylesheets/observ/_prompts.scss +485 -269
  18. data/app/assets/stylesheets/observ/_reset.scss +249 -0
  19. data/app/assets/stylesheets/observ/_table.scss +46 -38
  20. data/app/assets/stylesheets/observ/_variables.scss +54 -0
  21. data/app/assets/stylesheets/observ/application.scss +3 -0
  22. data/app/controllers/observ/dataset_run_items_controller.rb +0 -1
  23. data/app/controllers/observ/review_queue_controller.rb +154 -0
  24. data/app/controllers/observ/scores_controller.rb +64 -0
  25. data/app/controllers/observ/sessions_controller.rb +23 -0
  26. data/app/helpers/observ/application_helper.rb +1 -0
  27. data/app/helpers/observ/reviews_helper.rb +33 -0
  28. data/app/models/concerns/observ/json_queryable.rb +138 -0
  29. data/app/models/concerns/observ/reviewable.rb +41 -0
  30. data/app/models/concerns/observ/scoreable.rb +34 -0
  31. data/app/models/observ/dataset_run_item.rb +3 -13
  32. data/app/models/observ/review_item.rb +48 -0
  33. data/app/models/observ/score.rb +38 -6
  34. data/app/models/observ/session.rb +5 -1
  35. data/app/models/observ/trace.rb +3 -0
  36. data/app/services/observ/evaluators/base_evaluator.rb +0 -1
  37. data/app/services/observ/guardrail_service.rb +128 -0
  38. data/app/views/kaminari/_first_page.html.erb +1 -1
  39. data/app/views/kaminari/_gap.html.erb +1 -1
  40. data/app/views/kaminari/_last_page.html.erb +1 -1
  41. data/app/views/kaminari/_next_page.html.erb +1 -1
  42. data/app/views/kaminari/_page.html.erb +1 -1
  43. data/app/views/kaminari/_paginator.html.erb +1 -1
  44. data/app/views/kaminari/_prev_page.html.erb +1 -1
  45. data/app/views/kaminari/observ/_first_page.html.erb +1 -1
  46. data/app/views/kaminari/observ/_gap.html.erb +1 -1
  47. data/app/views/kaminari/observ/_last_page.html.erb +1 -1
  48. data/app/views/kaminari/observ/_next_page.html.erb +1 -1
  49. data/app/views/kaminari/observ/_page.html.erb +1 -1
  50. data/app/views/kaminari/observ/_paginator.html.erb +1 -1
  51. data/app/views/kaminari/observ/_prev_page.html.erb +1 -1
  52. data/app/views/layouts/observ/application.html.erb +96 -58
  53. data/app/views/observ/annotations/_form.html.erb +5 -5
  54. data/app/views/observ/annotations/index.html.erb +4 -4
  55. data/app/views/observ/annotations/sessions_index.html.erb +9 -9
  56. data/app/views/observ/annotations/traces_index.html.erb +9 -9
  57. data/app/views/observ/chats/_form.html.erb +7 -7
  58. data/app/views/observ/datasets/index.html.erb +6 -6
  59. data/app/views/observ/messages/_form.html.erb +11 -12
  60. data/app/views/observ/observations/index.html.erb +3 -4
  61. data/app/views/observ/prompts/_form.html.erb +37 -38
  62. data/app/views/observ/prompts/_new_form.html.erb +37 -38
  63. data/app/views/observ/prompts/compare.html.erb +59 -55
  64. data/app/views/observ/prompts/edit.html.erb +3 -3
  65. data/app/views/observ/prompts/index.html.erb +9 -9
  66. data/app/views/observ/prompts/new.html.erb +3 -3
  67. data/app/views/observ/prompts/show.html.erb +2 -2
  68. data/app/views/observ/prompts/versions.html.erb +22 -22
  69. data/app/views/observ/review_queue/_item.html.erb +39 -0
  70. data/app/views/observ/review_queue/_stats.html.erb +18 -0
  71. data/app/views/observ/review_queue/index.html.erb +49 -0
  72. data/app/views/observ/review_queue/show.html.erb +76 -0
  73. data/app/views/observ/review_queue/stats.html.erb +100 -0
  74. data/app/views/observ/scores/_form.html.erb +39 -0
  75. data/app/views/observ/scores/create.turbo_stream.erb +10 -0
  76. data/app/views/observ/sessions/_chat.html.erb +59 -0
  77. data/app/views/observ/sessions/_metadata.html.erb +17 -0
  78. data/app/views/observ/sessions/_metrics.html.erb +81 -0
  79. data/app/views/observ/sessions/_traces.html.erb +92 -0
  80. data/app/views/observ/sessions/annotations_drawer.turbo_stream.erb +8 -1
  81. data/app/views/observ/sessions/index.html.erb +60 -4
  82. data/app/views/observ/sessions/show.html.erb +4 -217
  83. data/app/views/observ/traces/_details.html.erb +47 -0
  84. data/app/views/observ/traces/_input.html.erb +10 -0
  85. data/app/views/observ/traces/_metadata.html.erb +10 -0
  86. data/app/views/observ/traces/_observations.html.erb +172 -0
  87. data/app/views/observ/traces/_output.html.erb +10 -0
  88. data/app/views/observ/traces/annotations_drawer.turbo_stream.erb +8 -1
  89. data/app/views/observ/traces/index.html.erb +3 -4
  90. data/app/views/observ/traces/show.html.erb +5 -232
  91. data/config/routes.rb +14 -0
  92. data/db/migrate/015_refactor_scores_to_polymorphic.rb +27 -0
  93. data/db/migrate/016_create_observ_review_items.rb +25 -0
  94. data/lib/observ/version.rb +1 -1
  95. metadata +30 -1
@@ -1,256 +1,521 @@
1
1
  @import 'variables';
2
2
 
3
+ // Sidebar width
4
+ $observ-sidebar-width: 220px;
5
+
6
+ // The root layout class - this one stays at the top level
3
7
  .observ-layout {
4
8
  min-height: 100vh;
5
9
  display: flex;
6
- flex-direction: column;
7
- background-color: $observ-gray-50;
10
+ background-color: $observ-bg-page;
8
11
  font-family: $observ-font-family;
9
- color: $observ-gray-900;
12
+ color: $observ-text-primary;
10
13
  font-size: $observ-font-size-base;
11
14
  line-height: 1.5;
12
- }
15
+ -webkit-font-smoothing: antialiased;
16
+ -moz-osx-font-smoothing: grayscale;
17
+
18
+ // ==========================================================================
19
+ // SIDEBAR NAVIGATION
20
+ // ==========================================================================
21
+ .observ-sidebar {
22
+ width: $observ-sidebar-width;
23
+ min-height: 100vh;
24
+ background-color: $observ-bg-surface;
25
+ border-right: 1px solid $observ-border-color;
26
+ display: flex;
27
+ flex-direction: column;
28
+ position: fixed;
29
+ left: 0;
30
+ top: 0;
31
+ bottom: 0;
32
+ z-index: 100;
33
+
34
+ &__header {
35
+ padding: $observ-spacing-lg;
36
+ border-bottom: 1px solid $observ-border-subtle;
37
+ }
38
+
39
+ &__brand {
40
+ font-size: $observ-font-size-lg;
41
+ font-weight: 700;
42
+ color: $observ-text-primary;
43
+ text-decoration: none;
44
+ display: block;
45
+ letter-spacing: -0.01em;
46
+
47
+ &:hover {
48
+ color: $observ-primary;
49
+ text-decoration: none;
50
+ }
51
+ }
52
+
53
+ &__nav {
54
+ flex: 1;
55
+ padding: $observ-spacing-md 0;
56
+ overflow-y: auto;
57
+ }
58
+
59
+ &__section {
60
+ margin-bottom: $observ-spacing-md;
61
+
62
+ &:last-child {
63
+ margin-bottom: 0;
64
+ }
65
+ }
66
+
67
+ &__section-title {
68
+ padding: $observ-spacing-sm $observ-spacing-lg;
69
+ font-size: $observ-font-size-xs;
70
+ font-weight: 600;
71
+ color: $observ-text-muted;
72
+ text-transform: uppercase;
73
+ letter-spacing: 0.05em;
74
+ }
75
+
76
+ &__menu {
77
+ list-style: none;
78
+ margin: 0;
79
+ padding: 0;
80
+ }
81
+
82
+ &__item {
83
+ margin: 0;
84
+ padding: 0;
85
+ }
86
+
87
+ &__link {
88
+ display: flex;
89
+ align-items: center;
90
+ justify-content: space-between;
91
+ padding: $observ-spacing-sm $observ-spacing-lg;
92
+ color: $observ-text-secondary;
93
+ text-decoration: none;
94
+ font-size: $observ-font-size-sm;
95
+ font-weight: 500;
96
+ border-left: 3px solid transparent;
97
+ transition: $observ-transition;
98
+
99
+ &:hover {
100
+ color: $observ-text-primary;
101
+ background-color: $observ-bg-hover;
102
+ text-decoration: none;
103
+ }
104
+
105
+ &--active {
106
+ color: $observ-text-primary;
107
+ background-color: $observ-bg-elevated;
108
+ border-left-color: $observ-primary;
109
+
110
+ &:hover {
111
+ background-color: $observ-bg-elevated;
112
+ }
113
+ }
114
+ }
115
+
116
+ &__badge {
117
+ display: inline-flex;
118
+ align-items: center;
119
+ justify-content: center;
120
+ min-width: 20px;
121
+ height: 20px;
122
+ padding: 0 $observ-spacing-xs;
123
+ font-size: $observ-font-size-xs;
124
+ font-weight: 600;
125
+ background-color: $observ-danger;
126
+ color: $observ-white;
127
+ border-radius: 10px;
128
+ }
129
+
130
+ &__footer {
131
+ padding: $observ-spacing-md $observ-spacing-lg;
132
+ border-top: 1px solid $observ-border-subtle;
133
+ margin-top: auto;
134
+ }
135
+
136
+ &__back-link {
137
+ display: flex;
138
+ align-items: center;
139
+ gap: $observ-spacing-xs;
140
+ color: $observ-text-muted;
141
+ text-decoration: none;
142
+ font-size: $observ-font-size-sm;
143
+ transition: $observ-transition;
144
+
145
+ &:hover {
146
+ color: $observ-text-secondary;
147
+ text-decoration: none;
148
+ }
149
+ }
150
+ }
13
151
 
14
- .observ-nav {
15
- background-color: $observ-white;
16
- border-bottom: 1px solid $observ-gray-200;
17
- box-shadow: $observ-shadow-sm;
152
+ // ==========================================================================
153
+ // MAIN CONTENT WRAPPER
154
+ // ==========================================================================
155
+ .observ-main-wrapper {
156
+ flex: 1;
157
+ margin-left: $observ-sidebar-width;
158
+ display: flex;
159
+ flex-direction: column;
160
+ min-height: 100vh;
161
+ }
18
162
 
19
- &__container {
163
+ .observ-main {
164
+ flex: 1;
20
165
  max-width: 1440px;
21
- margin: 0 auto;
22
- padding: 0 $observ-spacing-lg;
166
+ width: 100%;
167
+ padding: $observ-spacing-xl $observ-spacing-lg;
168
+ }
169
+
170
+ .observ-container {
23
171
  display: flex;
24
- align-items: center;
25
- justify-content: space-between;
26
- height: 64px;
172
+ flex-direction: column;
173
+ gap: $observ-spacing-xl;
27
174
  }
28
175
 
29
- &__brand {
30
- font-size: $observ-font-size-xl;
31
- font-weight: 600;
176
+ .observ-footer {
177
+ background-color: $observ-bg-surface;
178
+ border-top: 1px solid $observ-border-subtle;
179
+ margin-top: auto;
180
+
181
+ &__container {
182
+ max-width: 1440px;
183
+ padding: $observ-spacing-lg;
184
+ text-align: center;
185
+ }
186
+
187
+ &__text {
188
+ color: $observ-text-muted;
189
+ font-size: $observ-font-size-sm;
190
+ margin: 0;
191
+ }
32
192
  }
33
193
 
34
- &__brand-link {
35
- color: $observ-gray-900;
36
- text-decoration: none;
37
- transition: $observ-transition;
194
+ .observ-page {
195
+ &__header {
196
+ display: flex;
197
+ align-items: center;
198
+ justify-content: space-between;
199
+ margin-bottom: $observ-spacing-lg;
200
+ }
38
201
 
39
- &:hover {
40
- color: $observ-primary;
202
+ &__content {
203
+ display: flex;
204
+ flex-direction: column;
205
+ gap: $observ-spacing-lg;
206
+ }
207
+
208
+ &__title {
209
+ font-size: $observ-font-size-2xl;
210
+ font-weight: 700;
211
+ margin: 0;
212
+ color: $observ-text-primary;
213
+ }
214
+
215
+ &__breadcrumb {
216
+ font-size: $observ-font-size-sm;
217
+ color: $observ-text-muted;
218
+ margin-bottom: $observ-spacing-xs;
219
+
220
+ a {
221
+ color: $observ-primary;
222
+ text-decoration: none;
223
+
224
+ &:hover {
225
+ text-decoration: underline;
226
+ }
227
+ }
228
+ }
229
+
230
+ &__actions {
231
+ display: flex;
232
+ gap: $observ-spacing-sm;
41
233
  }
42
234
  }
43
235
 
44
- &__menu {
236
+ .observ-page-header {
237
+ margin-bottom: $observ-spacing-xl;
45
238
  display: flex;
46
- list-style: none;
47
- margin: 0;
48
- padding: 0;
239
+ align-items: center;
49
240
  gap: $observ-spacing-md;
50
- }
51
241
 
52
- &__item {
53
- margin: 0;
242
+ &__content {
243
+ display: flex;
244
+ align-items: center;
245
+ flex-wrap: wrap;
246
+ gap: $observ-spacing-md;
247
+ flex: 1;
248
+ }
249
+
250
+ &__breadcrumb {
251
+ color: $observ-text-muted;
252
+ font-size: $observ-font-size-sm;
253
+ margin-bottom: $observ-spacing-xs;
254
+ }
54
255
 
55
- &--dropdown {
56
- position: relative;
256
+ &__title {
257
+ font-size: $observ-font-size-3xl;
258
+ font-weight: 700;
259
+ margin: 0;
260
+ color: $observ-text-primary;
261
+ letter-spacing: -0.025em;
262
+ line-height: 1.2;
57
263
  }
58
- }
59
264
 
60
- &__dropdown-toggle {
61
- background: none;
62
- border: none;
63
- cursor: pointer;
64
- font-family: inherit;
65
- font-size: inherit;
66
- }
265
+ &__meta {
266
+ display: flex;
267
+ align-items: center;
268
+ gap: $observ-spacing-sm;
269
+ }
67
270
 
68
- &__dropdown-arrow {
69
- margin-left: 4px;
70
- font-size: 0.75em;
71
- transition: transform 0.2s;
271
+ &__actions {
272
+ display: flex;
273
+ gap: $observ-spacing-sm;
274
+ align-items: center;
275
+ }
72
276
  }
73
277
 
74
- &__dropdown-menu {
75
- position: absolute;
76
- top: 100%;
77
- left: 0;
78
- margin-top: 8px;
79
- background-color: $observ-white;
80
- border: 1px solid $observ-gray-200;
278
+ .observ-alert {
279
+ padding: $observ-spacing-md;
81
280
  border-radius: $observ-border-radius;
82
- box-shadow: $observ-shadow-md;
83
- list-style: none;
84
- padding: $observ-spacing-xs 0;
85
- min-width: 160px;
86
- opacity: 0;
87
- visibility: hidden;
88
- transform: translateY(-8px);
89
- transition: all 0.2s ease;
90
- z-index: 1000;
91
-
92
- &--open {
93
- opacity: 1;
94
- visibility: visible;
95
- transform: translateY(0);
96
- }
97
-
98
- li {
99
- margin: 0;
281
+ margin-bottom: $observ-spacing-lg;
282
+ font-size: $observ-font-size-sm;
283
+ border-left: 3px solid;
284
+
285
+ &--success {
286
+ background-color: rgba($observ-success, 0.1);
287
+ color: lighten($observ-success, 15%);
288
+ border-left-color: $observ-success;
100
289
  }
101
- }
102
290
 
103
- &__dropdown-link {
104
- display: block;
105
- padding: $observ-spacing-sm $observ-spacing-md;
106
- color: $observ-gray-700;
107
- text-decoration: none;
108
- transition: $observ-transition;
109
- font-weight: 500;
110
- white-space: nowrap;
291
+ &--danger {
292
+ background-color: rgba($observ-danger, 0.1);
293
+ color: lighten($observ-danger, 15%);
294
+ border-left-color: $observ-danger;
295
+ }
111
296
 
112
- &:hover {
113
- background-color: $observ-gray-100;
114
- color: $observ-gray-900;
297
+ &--warning {
298
+ background-color: rgba($observ-warning, 0.1);
299
+ color: lighten($observ-warning, 10%);
300
+ border-left-color: $observ-warning;
301
+ }
302
+
303
+ &--info {
304
+ background-color: rgba($observ-info, 0.1);
305
+ color: lighten($observ-info, 15%);
306
+ border-left-color: $observ-info;
115
307
  }
116
308
  }
117
309
 
118
- &__link {
119
- display: block;
120
- padding: $observ-spacing-sm $observ-spacing-md;
121
- color: $observ-gray-600;
122
- text-decoration: none;
123
- border-radius: $observ-border-radius-sm;
124
- transition: $observ-transition;
125
- font-weight: 500;
310
+ .observ-grid {
311
+ display: grid;
312
+ gap: $observ-spacing-lg;
126
313
 
127
- &:hover {
128
- background-color: $observ-gray-100;
129
- color: $observ-gray-900;
314
+ &--2 {
315
+ grid-template-columns: repeat(2, 1fr);
130
316
  }
131
317
 
132
- &--active {
133
- background-color: $observ-primary;
134
- color: $observ-white;
318
+ &--3 {
319
+ grid-template-columns: repeat(3, 1fr);
320
+ }
135
321
 
136
- &:hover {
137
- background-color: darken($observ-primary, 5%);
138
- color: $observ-white;
139
- }
322
+ &--4 {
323
+ grid-template-columns: repeat(4, 1fr);
140
324
  }
325
+ }
141
326
 
142
- &--secondary {
143
- color: $observ-gray-500;
327
+ .observ-empty-state {
328
+ text-align: center;
329
+ padding: $observ-spacing-2xl;
330
+
331
+ &__text {
332
+ font-size: $observ-font-size-lg;
333
+ color: $observ-text-secondary;
334
+ margin: 0 0 $observ-spacing-sm;
335
+ }
336
+
337
+ &__subtext {
144
338
  font-size: $observ-font-size-sm;
339
+ color: $observ-text-muted;
340
+ margin: 0;
145
341
  }
146
342
  }
147
343
 
148
- &__actions {
344
+ .observ-stats-bar {
149
345
  display: flex;
150
- gap: $observ-spacing-sm;
346
+ gap: $observ-spacing-lg;
347
+ padding: $observ-spacing-md;
348
+ background-color: $observ-bg-surface;
349
+ border: 1px solid $observ-border-subtle;
350
+ border-radius: $observ-border-radius;
351
+ margin-bottom: $observ-spacing-lg;
151
352
  }
152
- }
153
353
 
154
- .observ-main {
155
- flex: 1;
156
- max-width: 1440px;
157
- width: 100%;
158
- margin: 0 auto;
159
- padding: $observ-spacing-xl $observ-spacing-lg;
160
- }
354
+ .observ-stat {
355
+ display: flex;
356
+ flex-direction: column;
357
+ align-items: center;
358
+ gap: $observ-spacing-xs;
161
359
 
162
- .observ-container {
163
- display: flex;
164
- flex-direction: column;
165
- gap: $observ-spacing-xl;
166
- }
360
+ &__value {
361
+ font-size: $observ-font-size-xl;
362
+ font-weight: 600;
363
+ color: $observ-text-primary;
167
364
 
168
- .observ-footer {
169
- background-color: $observ-white;
170
- border-top: 1px solid $observ-gray-200;
171
- margin-top: auto;
365
+ &--large {
366
+ font-size: $observ-font-size-3xl;
367
+ }
368
+ }
172
369
 
173
- &__container {
174
- max-width: 1440px;
175
- margin: 0 auto;
176
- padding: $observ-spacing-lg;
177
- text-align: center;
370
+ &__label {
371
+ font-size: $observ-font-size-sm;
372
+ color: $observ-text-muted;
373
+ }
178
374
  }
179
375
 
180
- &__text {
181
- color: $observ-gray-500;
182
- font-size: $observ-font-size-sm;
183
- margin: 0;
184
- }
185
- }
376
+ .observ-details-list {
377
+ display: grid;
378
+ grid-template-columns: auto 1fr;
379
+ gap: $observ-spacing-xs $observ-spacing-md;
186
380
 
187
- .observ-page-header {
188
- margin-bottom: $observ-spacing-xl;
189
- display: flex;
190
- align-items: center;
191
- gap: $observ-spacing-md;
381
+ dt {
382
+ font-weight: 500;
383
+ color: $observ-text-muted;
384
+ }
192
385
 
193
- &__content {
194
- display: flex;
195
- align-items: center;
196
- flex-wrap: wrap;
197
- gap: $observ-spacing-md;
198
- flex: 1;
386
+ dd {
387
+ margin: 0;
388
+ color: $observ-text-primary;
389
+ }
199
390
  }
200
391
 
201
- &__breadcrumb {
202
- color: $observ-gray-500;
203
- font-size: $observ-font-size-sm;
204
- margin-bottom: $observ-spacing-xs;
392
+ .observ-review-layout {
393
+ display: grid;
394
+ grid-template-columns: 1fr 300px;
395
+ gap: $observ-spacing-lg;
205
396
  }
206
397
 
207
- &__title {
208
- font-size: $observ-font-size-3xl;
209
- font-weight: 700;
210
- margin: 0;
211
- color: $observ-gray-900;
398
+ .observ-review-main {
399
+ display: flex;
400
+ flex-direction: column;
401
+ gap: $observ-spacing-lg;
212
402
  }
213
403
 
214
- &__meta {
404
+ .observ-review-sidebar {
215
405
  display: flex;
216
- align-items: center;
217
- gap: $observ-spacing-sm;
406
+ flex-direction: column;
407
+ gap: $observ-spacing-lg;
408
+ position: sticky;
409
+ top: $observ-spacing-lg;
410
+ align-self: flex-start;
411
+ max-height: calc(100vh - 2 * #{$observ-spacing-lg});
412
+ overflow-y: auto;
218
413
  }
219
414
 
220
- &__actions {
415
+ .observ-review-actions {
221
416
  display: flex;
417
+ flex-direction: column;
222
418
  gap: $observ-spacing-sm;
223
- align-items: center;
224
419
  }
225
- }
226
420
 
227
- .observ-alert {
228
- padding: $observ-spacing-md;
229
- border-radius: $observ-border-radius;
230
- margin-bottom: $observ-spacing-lg;
231
- font-size: $observ-font-size-sm;
421
+ .observ-review-preview {
422
+ max-height: 400px;
423
+ overflow-y: auto;
424
+ }
425
+
426
+ .observ-review-preview-notice {
427
+ margin-top: $observ-spacing-md;
428
+ text-align: center;
429
+ }
430
+
431
+ .observ-score-form {
432
+ .observ-form-field {
433
+ margin-bottom: $observ-spacing-md;
434
+ }
435
+
436
+ .observ-form-label {
437
+ display: block;
438
+ margin-bottom: $observ-spacing-xs;
439
+ font-weight: 500;
440
+ color: $observ-text-secondary;
441
+ font-size: $observ-font-size-sm;
442
+ }
443
+
444
+ .observ-form-textarea {
445
+ width: 100%;
446
+ padding: $observ-spacing-sm;
447
+ border: 1px solid $observ-border-color;
448
+ border-radius: $observ-border-radius-sm;
449
+ font-family: inherit;
450
+ font-size: $observ-font-size-sm;
451
+ resize: vertical;
452
+ background-color: $observ-bg-elevated;
453
+ color: $observ-text-primary;
454
+
455
+ &:focus {
456
+ outline: none;
457
+ border-color: $observ-primary;
458
+ box-shadow: 0 0 0 3px rgba($observ-primary, 0.1);
459
+ }
460
+ }
232
461
 
233
- &--success {
234
- background-color: lighten($observ-success, 45%);
235
- color: darken($observ-success, 20%);
236
- border: 1px solid lighten($observ-success, 30%);
462
+ .observ-form-actions {
463
+ display: flex;
464
+ gap: $observ-spacing-sm;
465
+ }
237
466
  }
238
467
 
239
- &--danger {
240
- background-color: lighten($observ-danger, 45%);
241
- color: darken($observ-danger, 20%);
242
- border: 1px solid lighten($observ-danger, 30%);
468
+ .observ-score-buttons {
469
+ display: flex;
470
+ gap: $observ-spacing-sm;
243
471
  }
244
472
 
245
- &--warning {
246
- background-color: lighten($observ-warning, 45%);
247
- color: darken($observ-warning, 30%);
248
- border: 1px solid lighten($observ-warning, 30%);
473
+ .observ-score-button {
474
+ display: flex;
475
+ align-items: center;
476
+ gap: $observ-spacing-xs;
477
+ padding: $observ-spacing-sm $observ-spacing-md;
478
+ border: 1px solid $observ-border-color;
479
+ border-radius: $observ-border-radius-sm;
480
+ background-color: $observ-bg-elevated;
481
+ color: $observ-text-primary;
482
+ cursor: pointer;
483
+ transition: $observ-transition;
484
+
485
+ input[type="radio"] {
486
+ position: absolute;
487
+ opacity: 0;
488
+ width: 0;
489
+ height: 0;
490
+ }
491
+
492
+ &:hover {
493
+ border-color: $observ-border-strong;
494
+ }
495
+
496
+ // Highlight when radio inside is checked (CSS :has selector)
497
+ &:has(input[type="radio"]:checked) {
498
+ border-color: $observ-primary;
499
+ background-color: rgba($observ-primary, 0.15);
500
+ box-shadow: 0 0 0 3px rgba($observ-primary, 0.1);
501
+ }
502
+
503
+ // Keep legacy --selected modifier for backwards compatibility
504
+ &--selected {
505
+ border-color: $observ-primary;
506
+ background-color: rgba($observ-primary, 0.15);
507
+ }
249
508
  }
250
509
 
251
- &--info {
252
- background-color: lighten($observ-info, 45%);
253
- color: darken($observ-info, 20%);
254
- border: 1px solid lighten($observ-info, 30%);
510
+ .observ-score-icon {
511
+ font-size: $observ-font-size-lg;
512
+
513
+ &--pass {
514
+ color: $observ-success;
515
+ }
516
+
517
+ &--fail {
518
+ color: $observ-danger;
519
+ }
255
520
  }
256
521
  }