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,326 +1,324 @@
1
1
  @import 'variables';
2
+ @import 'namespace';
2
3
 
3
- .observ-badge {
4
- display: inline-flex;
5
- align-items: center;
6
- padding: 0.25rem 0.75rem;
7
- border-radius: 9999px;
8
- font-size: $observ-font-size-xs;
9
- font-weight: 600;
10
- text-transform: uppercase;
11
- letter-spacing: 0.05em;
12
-
13
- &--success {
14
- background-color: lighten($observ-success, 45%);
15
- color: darken($observ-success, 10%);
16
- }
4
+ @include observ-scoped {
5
+ .observ-badge {
6
+ display: inline-flex;
7
+ align-items: center;
8
+ padding: 0.25rem 0.75rem;
9
+ border-radius: 9999px;
10
+ font-size: $observ-font-size-xs;
11
+ font-weight: 600;
12
+ text-transform: uppercase;
13
+ letter-spacing: 0.05em;
17
14
 
18
- &--danger {
19
- background-color: lighten($observ-danger, 45%);
20
- color: darken($observ-danger, 10%);
21
- }
15
+ &--success {
16
+ background-color: rgba($observ-success, 0.15);
17
+ color: lighten($observ-success, 15%);
18
+ }
22
19
 
23
- &--warning {
24
- background-color: lighten($observ-warning, 45%);
25
- color: darken($observ-warning, 30%);
26
- }
20
+ &--danger {
21
+ background-color: rgba($observ-danger, 0.15);
22
+ color: lighten($observ-danger, 15%);
23
+ }
27
24
 
28
- &--info {
29
- background-color: lighten($observ-info, 45%);
30
- color: darken($observ-info, 10%);
31
- }
25
+ &--warning {
26
+ background-color: rgba($observ-warning, 0.15);
27
+ color: lighten($observ-warning, 10%);
28
+ }
32
29
 
33
- &--generation {
34
- background-color: #e0e7ff;
35
- color: #4338ca;
36
- }
30
+ &--info {
31
+ background-color: rgba($observ-info, 0.15);
32
+ color: lighten($observ-info, 15%);
33
+ }
37
34
 
38
- &--span {
39
- background-color: lighten($observ-info, 45%);
40
- color: darken($observ-info, 10%);
41
- }
35
+ &--default {
36
+ background-color: $observ-bg-elevated;
37
+ color: $observ-text-secondary;
38
+ }
42
39
 
43
- &--light {
44
- background-color: rgba(255, 255, 255, 0.3);
45
- color: $observ-white;
46
- font-size: 0.75rem;
47
- padding: 0.125rem 0.5rem;
48
- margin-left: 0.375rem;
49
- }
50
- }
40
+ &--secondary {
41
+ background-color: $observ-bg-elevated;
42
+ color: $observ-text-secondary;
43
+ }
51
44
 
52
- .observ-button {
53
- display: inline-flex;
54
- align-items: center;
55
- justify-content: center;
56
- gap: $observ-spacing-xs;
57
- padding: $observ-spacing-sm $observ-spacing-md;
58
- border: 1px solid $observ-gray-300;
59
- border-radius: $observ-border-radius-sm;
60
- background-color: $observ-white;
61
- color: $observ-gray-700;
62
- font-size: $observ-font-size-sm;
63
- font-weight: 500;
64
- font-family: inherit;
65
- line-height: 1.25;
66
- text-decoration: none;
67
- cursor: pointer;
68
- transition: $observ-transition;
69
-
70
- &:hover {
71
- background-color: $observ-gray-50;
72
- border-color: $observ-gray-400;
73
- }
45
+ &--generation {
46
+ background-color: rgba($observ-purple, 0.15);
47
+ color: $observ-purple;
48
+ }
74
49
 
75
- &:active {
76
- background-color: $observ-gray-100;
77
- }
50
+ &--span {
51
+ background-color: rgba($observ-info, 0.15);
52
+ color: lighten($observ-info, 15%);
53
+ }
78
54
 
79
- &--sm {
80
- padding: 0.375rem 0.75rem;
81
- font-size: $observ-font-size-xs;
55
+ &--light {
56
+ background-color: rgba(255, 255, 255, 0.15);
57
+ color: $observ-white;
58
+ font-size: 0.75rem;
59
+ padding: 0.125rem 0.5rem;
60
+ margin-left: 0.375rem;
61
+ }
82
62
  }
83
63
 
84
- &--primary {
85
- background-color: $observ-primary;
86
- border-color: $observ-primary;
87
- color: $observ-white;
64
+ .observ-button {
65
+ display: inline-flex;
66
+ align-items: center;
67
+ justify-content: center;
68
+ gap: $observ-spacing-xs;
69
+ padding: $observ-spacing-sm $observ-spacing-md;
70
+ border: 1px solid $observ-border-color;
71
+ border-radius: $observ-border-radius-sm;
72
+ background-color: $observ-bg-elevated;
73
+ color: $observ-text-primary;
74
+ font-size: $observ-font-size-sm;
75
+ font-weight: 500;
76
+ font-family: inherit;
77
+ line-height: 1.25;
78
+ text-decoration: none;
79
+ cursor: pointer;
80
+ transition: $observ-transition;
88
81
 
89
82
  &:hover {
90
- background-color: darken($observ-primary, 5%);
91
- border-color: darken($observ-primary, 5%);
83
+ background-color: $observ-bg-hover;
84
+ border-color: $observ-border-strong;
85
+ color: $observ-text-primary;
86
+ text-decoration: none;
92
87
  }
93
88
 
94
- &:disabled {
95
- background-color: $observ-gray-300;
96
- border-color: $observ-gray-300;
97
- color: $observ-gray-500;
98
- cursor: not-allowed;
89
+ &:focus {
90
+ outline: none;
91
+ box-shadow: 0 0 0 3px rgba($observ-primary, 0.2);
92
+ border-color: $observ-primary;
93
+ }
94
+
95
+ &:active {
96
+ background-color: $observ-bg-surface;
97
+ }
98
+
99
+ &--sm,
100
+ &--small {
101
+ padding: 0.375rem 0.75rem;
102
+ font-size: $observ-font-size-xs;
103
+ }
104
+
105
+ &--block {
106
+ display: flex;
107
+ width: 100%;
108
+ }
109
+
110
+ &--link {
111
+ background-color: transparent;
112
+ border-color: transparent;
113
+ color: $observ-text-secondary;
99
114
 
100
115
  &:hover {
101
- background-color: $observ-gray-300;
102
- border-color: $observ-gray-300;
116
+ background-color: transparent;
117
+ border-color: transparent;
118
+ color: $observ-text-primary;
119
+ text-decoration: underline;
103
120
  }
104
121
  }
105
- }
106
122
 
107
- &--secondary {
108
- background-color: $observ-gray-100;
109
- border-color: $observ-gray-300;
110
- color: $observ-gray-700;
123
+ &--primary {
124
+ background-color: $observ-primary;
125
+ border-color: $observ-primary;
126
+ color: $observ-white;
127
+ box-shadow: 0 1px 3px rgba(59, 130, 246, 0.3);
128
+
129
+ &:hover {
130
+ background-color: darken($observ-primary, 5%);
131
+ border-color: darken($observ-primary, 5%);
132
+ color: $observ-white;
133
+ transform: translateY(-1px);
134
+ box-shadow: 0 4px 6px rgba(59, 130, 246, 0.4);
135
+ }
111
136
 
112
- &:hover {
113
- background-color: $observ-gray-200;
114
- border-color: $observ-gray-400;
137
+ &:disabled {
138
+ background-color: $observ-bg-elevated;
139
+ border-color: $observ-border-color;
140
+ color: $observ-text-muted;
141
+ cursor: not-allowed;
142
+ box-shadow: none;
143
+
144
+ &:hover {
145
+ background-color: $observ-bg-elevated;
146
+ border-color: $observ-border-color;
147
+ color: $observ-text-muted;
148
+ transform: none;
149
+ }
150
+ }
115
151
  }
116
- }
117
152
 
118
- &--danger {
119
- background-color: $observ-white;
120
- border-color: $observ-danger;
121
- color: $observ-danger;
153
+ &--secondary {
154
+ background-color: $observ-bg-elevated;
155
+ border-color: $observ-border-color;
156
+ color: $observ-text-secondary;
122
157
 
123
- &:hover {
124
- background-color: $observ-danger;
125
- border-color: $observ-danger;
126
- color: $observ-white;
158
+ &:hover {
159
+ background-color: $observ-bg-hover;
160
+ border-color: $observ-border-strong;
161
+ color: $observ-text-primary;
162
+ }
127
163
  }
128
- }
129
- }
130
164
 
131
- // Ensure button elements inside forms (from button_to) match link styles
132
- button.observ-button {
133
- appearance: none;
134
- -webkit-appearance: none;
135
- }
165
+ &--danger {
166
+ background-color: transparent;
167
+ border-color: $observ-danger;
168
+ color: $observ-danger;
136
169
 
137
- .observ-link {
138
- color: $observ-primary;
139
- text-decoration: none;
140
- font-weight: 500;
141
- transition: $observ-transition;
170
+ &:hover {
171
+ background-color: $observ-danger;
172
+ border-color: $observ-danger;
173
+ color: $observ-white;
174
+ }
175
+ }
176
+ }
142
177
 
143
- &:hover {
144
- color: darken($observ-primary, 10%);
145
- text-decoration: underline;
178
+ // Ensure button elements inside forms (from button_to) match link styles
179
+ button.observ-button {
180
+ appearance: none;
181
+ -webkit-appearance: none;
146
182
  }
147
- }
148
183
 
149
- .observ-code {
150
- font-family: $observ-font-family-mono;
151
- background-color: $observ-gray-100;
152
- padding: 0.125rem 0.375rem;
153
- border-radius: $observ-border-radius-sm;
154
- font-size: 0.875em;
184
+ .observ-link {
185
+ color: $observ-primary;
186
+ text-decoration: none;
187
+ font-weight: 500;
188
+ transition: $observ-transition;
155
189
 
156
- &--inline {
157
- display: inline;
190
+ &:hover {
191
+ color: darken($observ-primary, 10%);
192
+ text-decoration: underline;
193
+ }
158
194
  }
159
- }
160
-
161
- .observ-code-block {
162
- font-family: $observ-font-family-mono;
163
- background-color: $observ-gray-900;
164
- color: $observ-gray-100;
165
- padding: $observ-spacing-md;
166
- border-radius: $observ-border-radius;
167
- overflow-x: auto;
168
- font-size: $observ-font-size-sm;
169
- line-height: 1.6;
170
- margin: 0;
171
- white-space: pre-wrap;
172
- word-wrap: break-word;
173
- }
174
195
 
175
- .observ-model-badge {
176
- display: inline-flex;
177
- align-items: center;
178
- padding: 0.25rem 0.5rem;
179
- background-color: lighten($observ-primary, 45%);
180
- color: darken($observ-primary, 10%);
181
- border-radius: $observ-border-radius-sm;
182
- font-size: $observ-font-size-xs;
183
- font-weight: 600;
184
- font-family: $observ-font-family-mono;
185
- }
196
+ .observ-code {
197
+ font-family: $observ-font-family-mono;
198
+ background-color: $observ-bg-elevated;
199
+ color: $observ-text-primary;
200
+ padding: 0.125rem 0.375rem;
201
+ border-radius: $observ-border-radius-sm;
202
+ font-size: 0.875em;
186
203
 
187
- .observ-text {
188
- &--muted {
189
- color: $observ-gray-500;
204
+ &--inline {
205
+ display: inline;
206
+ }
190
207
  }
191
208
 
192
- &--small {
209
+ .observ-code-block {
210
+ font-family: $observ-font-family-mono;
211
+ background-color: $observ-bg-elevated;
212
+ border: 1px solid $observ-border-color;
213
+ color: $observ-text-primary;
214
+ padding: $observ-spacing-md;
215
+ border-radius: $observ-border-radius;
216
+ overflow-x: auto;
193
217
  font-size: $observ-font-size-sm;
194
- }
195
- }
196
-
197
- .observ-definition-list {
198
- display: flex;
199
- flex-direction: column;
200
- gap: $observ-spacing-md;
201
- margin: 0;
202
-
203
- &__item {
204
- display: flex;
205
- flex-direction: column;
206
- gap: $observ-spacing-xs;
218
+ line-height: 1.6;
219
+ margin: 0;
220
+ white-space: pre-wrap;
221
+ word-wrap: break-word;
207
222
  }
208
223
 
209
- &__term {
224
+ .observ-model-badge {
225
+ display: inline-flex;
226
+ align-items: center;
227
+ padding: 0.25rem 0.5rem;
228
+ background-color: rgba($observ-primary, 0.15);
229
+ color: $observ-primary;
230
+ border-radius: $observ-border-radius-sm;
210
231
  font-size: $observ-font-size-xs;
211
232
  font-weight: 600;
212
- color: $observ-gray-500;
213
- text-transform: uppercase;
214
- letter-spacing: 0.05em;
215
- margin: 0;
233
+ font-family: $observ-font-family-mono;
216
234
  }
217
235
 
218
- &__definition {
219
- font-size: $observ-font-size-base;
220
- color: $observ-gray-900;
221
- margin: 0;
222
- }
236
+ .observ-text {
237
+ &--muted {
238
+ color: $observ-text-muted;
239
+ }
223
240
 
224
- &--horizontal {
225
- flex-direction: row;
226
- flex-wrap: wrap;
241
+ &--small {
242
+ font-size: $observ-font-size-sm;
243
+ }
227
244
 
228
- .observ-definition-list__item {
229
- flex-direction: row;
230
- align-items: baseline;
231
- gap: $observ-spacing-sm;
232
- flex: 0 0 auto;
233
- min-width: 150px;
245
+ &--success {
246
+ color: lighten($observ-success, 10%);
234
247
  }
235
- }
236
- }
237
248
 
238
- .observ-input,
239
- .observ-select {
240
- width: 100%;
241
- max-width: 600px;
242
- padding: $observ-spacing-sm $observ-spacing-md;
243
- border: 1px solid $observ-gray-300;
244
- border-radius: $observ-border-radius-sm;
245
- background-color: $observ-white;
246
- color: $observ-gray-900;
247
- font-size: $observ-font-size-sm;
248
- line-height: 1.5;
249
- transition: $observ-transition;
250
- font-family: inherit;
251
-
252
- &:hover {
253
- border-color: $observ-gray-400;
254
- }
249
+ &--warning {
250
+ color: lighten($observ-warning, 5%);
251
+ }
255
252
 
256
- &:focus {
257
- outline: none;
258
- border-color: $observ-primary;
259
- box-shadow: 0 0 0 3px rgba($observ-primary, 0.1);
260
- }
253
+ &--danger {
254
+ color: lighten($observ-danger, 10%);
255
+ }
261
256
 
262
- &::placeholder {
263
- color: $observ-gray-400;
264
- }
257
+ &--center {
258
+ text-align: center;
259
+ }
265
260
 
266
- &:disabled {
267
- background-color: $observ-gray-50;
268
- color: $observ-gray-500;
269
- cursor: not-allowed;
261
+ &--right {
262
+ text-align: right;
263
+ }
270
264
  }
271
- }
272
-
273
- .observ-input--wide {
274
- max-width: 800px;
275
- }
276
265
 
277
- select.observ-input,
278
- .observ-select {
279
- appearance: none;
280
- background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%236b7280' d='M6 9L1 4h10z'/%3E%3C/svg%3E");
281
- background-repeat: no-repeat;
282
- background-position: right $observ-spacing-sm center;
283
- padding-right: calc(#{$observ-spacing-md} + 1.5rem);
284
- cursor: pointer;
285
- }
266
+ .observ-definition-list {
267
+ display: flex;
268
+ flex-direction: column;
269
+ gap: $observ-spacing-md;
270
+ margin: 0;
286
271
 
287
- .observ-label {
288
- display: block;
289
- margin-bottom: $observ-spacing-xs;
290
- font-size: $observ-font-size-sm;
291
- font-weight: 500;
292
- color: $observ-gray-700;
293
- }
272
+ &__item {
273
+ display: flex;
274
+ flex-direction: column;
275
+ gap: $observ-spacing-xs;
276
+ }
294
277
 
295
- .observ-form-group {
296
- margin-bottom: $observ-spacing-md;
297
- }
278
+ &__term {
279
+ font-size: $observ-font-size-sm;
280
+ font-weight: 600;
281
+ color: $observ-text-muted;
282
+ margin: 0;
283
+ }
298
284
 
299
- // BEM-style form components
300
- .observ-form {
301
- &__group {
302
- margin-bottom: $observ-spacing-lg;
303
- }
285
+ &__definition {
286
+ font-size: $observ-font-size-base;
287
+ color: $observ-text-primary;
288
+ margin: 0;
289
+ }
304
290
 
305
- &__label {
306
- display: block;
307
- margin-bottom: $observ-spacing-xs;
308
- font-size: $observ-font-size-sm;
309
- font-weight: 500;
310
- color: $observ-gray-700;
291
+ &--horizontal {
292
+ flex-direction: row;
293
+ flex-wrap: wrap;
294
+
295
+ .observ-definition-list__item {
296
+ flex-direction: row;
297
+ align-items: baseline;
298
+ gap: $observ-spacing-sm;
299
+ flex: 0 0 auto;
300
+ min-width: 150px;
301
+ }
302
+ }
311
303
  }
312
304
 
313
- &__input,
314
- &__select,
315
- &__textarea {
305
+ .observ-input,
306
+ .observ-select {
316
307
  width: 100%;
308
+ max-width: 600px;
317
309
  padding: $observ-spacing-sm $observ-spacing-md;
318
- font-size: $observ-font-size-base;
319
- color: $observ-gray-900;
320
- background-color: #fff;
321
- border: 1px solid $observ-gray-300;
310
+ border: 1px solid $observ-border-color;
322
311
  border-radius: $observ-border-radius-sm;
323
- transition: border-color 0.2s ease, box-shadow 0.2s ease;
312
+ background-color: $observ-bg-elevated;
313
+ color: $observ-text-primary;
314
+ font-family: $observ-font-family;
315
+ font-size: $observ-font-size-sm;
316
+ line-height: 1.5;
317
+ transition: $observ-transition;
318
+
319
+ &:hover {
320
+ border-color: $observ-border-strong;
321
+ }
324
322
 
325
323
  &:focus {
326
324
  outline: none;
@@ -328,123 +326,352 @@ select.observ-input,
328
326
  box-shadow: 0 0 0 3px rgba($observ-primary, 0.1);
329
327
  }
330
328
 
329
+ // Placeholder styling - full reset with vendor prefixes
331
330
  &::placeholder {
332
- color: $observ-gray-400;
331
+ color: $observ-text-muted;
332
+ opacity: 1;
333
+ font-family: $observ-font-family;
334
+ font-style: normal;
335
+ font-weight: 400;
336
+ text-transform: none;
337
+ letter-spacing: normal;
338
+ }
339
+
340
+ &::-webkit-input-placeholder {
341
+ color: $observ-text-muted;
342
+ opacity: 1;
343
+ font-family: $observ-font-family;
344
+ font-style: normal;
345
+ font-weight: 400;
346
+ text-transform: none;
347
+ letter-spacing: normal;
348
+ }
349
+
350
+ &::-moz-placeholder {
351
+ color: $observ-text-muted;
352
+ opacity: 1;
353
+ font-family: $observ-font-family;
354
+ font-style: normal;
355
+ font-weight: 400;
356
+ text-transform: none;
357
+ letter-spacing: normal;
358
+ }
359
+
360
+ &:-ms-input-placeholder {
361
+ color: $observ-text-muted;
362
+ opacity: 1;
363
+ font-family: $observ-font-family;
364
+ font-style: normal;
365
+ font-weight: 400;
366
+ text-transform: none;
367
+ letter-spacing: normal;
333
368
  }
334
369
 
335
370
  &:disabled {
336
- background-color: $observ-gray-100;
371
+ background-color: $observ-bg-surface;
372
+ color: $observ-text-muted;
337
373
  cursor: not-allowed;
338
374
  }
339
375
  }
340
376
 
341
- &__textarea {
342
- resize: vertical;
343
- min-height: 80px;
344
-
345
- &--code {
346
- font-family: $observ-font-family-mono;
347
- font-size: $observ-font-size-sm;
348
- }
377
+ .observ-input--wide {
378
+ max-width: 800px;
349
379
  }
350
380
 
351
- &__select {
352
- cursor: pointer;
381
+ select.observ-input,
382
+ .observ-select {
353
383
  appearance: none;
354
- background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%236b7280' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
384
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%239ca3af' d='M6 9L1 4h10z'/%3E%3C/svg%3E");
355
385
  background-repeat: no-repeat;
356
- background-position: right $observ-spacing-md center;
357
- padding-right: calc($observ-spacing-md + 1.5rem);
386
+ background-position: right $observ-spacing-sm center;
387
+ padding-right: calc(#{$observ-spacing-md} + 1.5rem);
388
+ cursor: pointer;
358
389
  }
359
390
 
360
- &__hint {
361
- margin-top: $observ-spacing-xs;
391
+ .observ-label {
392
+ display: block;
393
+ margin-bottom: $observ-spacing-xs;
362
394
  font-size: $observ-font-size-sm;
363
- color: $observ-gray-500;
364
-
365
- &--warning {
366
- color: $observ-warning;
367
- }
395
+ font-weight: 500;
396
+ color: $observ-text-secondary;
368
397
  }
369
398
 
370
- &__actions {
371
- display: flex;
372
- align-items: center;
373
- gap: $observ-spacing-md;
374
- padding-top: $observ-spacing-lg;
375
- border-top: 1px solid $observ-gray-200;
376
- margin-top: $observ-spacing-lg;
399
+ .observ-form-group {
400
+ margin-bottom: $observ-spacing-md;
377
401
  }
378
- }
379
402
 
380
- .observ-alert__title {
381
- margin: 0 0 $observ-spacing-sm 0;
382
- font-size: $observ-font-size-base;
383
- font-weight: 600;
384
- }
403
+ // BEM-style form components
404
+ .observ-form {
405
+ &__group {
406
+ margin-bottom: $observ-spacing-lg;
407
+ }
385
408
 
386
- .observ-alert__list {
387
- margin: 0;
388
- padding-left: $observ-spacing-lg;
389
- }
409
+ &__label {
410
+ display: block;
411
+ margin-bottom: $observ-spacing-xs;
412
+ font-size: $observ-font-size-sm;
413
+ font-weight: 500;
414
+ color: $observ-text-secondary;
415
+ }
390
416
 
391
- .observ-form-separator {
392
- border-top: 1px solid $observ-gray-200;
393
- padding-top: $observ-spacing-lg;
394
- margin-top: $observ-spacing-lg;
395
- }
417
+ &__input,
418
+ &__select,
419
+ &__textarea {
420
+ width: 100%;
421
+ padding: $observ-spacing-sm $observ-spacing-md;
422
+ font-family: $observ-font-family;
423
+ font-size: $observ-font-size-base;
424
+ line-height: 1.5;
425
+ color: $observ-text-primary;
426
+ background-color: $observ-bg-elevated;
427
+ border: 1px solid $observ-border-color;
428
+ border-radius: $observ-border-radius-sm;
429
+ transition: border-color 0.2s ease, box-shadow 0.2s ease;
430
+
431
+ &:focus {
432
+ outline: none;
433
+ border-color: $observ-primary;
434
+ box-shadow: 0 0 0 3px rgba($observ-primary, 0.1);
435
+ }
396
436
 
397
- .observ-form-actions {
398
- display: flex;
399
- align-items: center;
400
- gap: $observ-spacing-md;
401
- }
437
+ // Placeholder styling - full reset with vendor prefixes
438
+ &::placeholder {
439
+ color: $observ-text-muted;
440
+ opacity: 1;
441
+ font-family: $observ-font-family;
442
+ font-style: normal;
443
+ font-weight: 400;
444
+ text-transform: none;
445
+ letter-spacing: normal;
446
+ }
402
447
 
403
- .observ-loading-indicator {
404
- display: inline-flex;
405
- align-items: center;
406
- gap: $observ-spacing-sm;
407
- color: $observ-gray-600;
408
- font-size: $observ-font-size-sm;
409
- }
448
+ &::-webkit-input-placeholder {
449
+ color: $observ-text-muted;
450
+ opacity: 1;
451
+ font-family: $observ-font-family;
452
+ font-style: normal;
453
+ font-weight: 400;
454
+ text-transform: none;
455
+ letter-spacing: normal;
456
+ }
410
457
 
411
- .observ-spinner {
412
- display: inline-block;
413
- width: 1rem;
414
- height: 1rem;
415
- border: 2px solid $observ-gray-300;
416
- border-top-color: $observ-primary;
417
- border-radius: 50%;
418
- animation: observ-spin 0.8s linear infinite;
419
- }
458
+ &::-moz-placeholder {
459
+ color: $observ-text-muted;
460
+ opacity: 1;
461
+ font-family: $observ-font-family;
462
+ font-style: normal;
463
+ font-weight: 400;
464
+ text-transform: none;
465
+ letter-spacing: normal;
466
+ }
420
467
 
421
- @keyframes observ-spin {
422
- to {
423
- transform: rotate(360deg);
468
+ &:-ms-input-placeholder {
469
+ color: $observ-text-muted;
470
+ opacity: 1;
471
+ font-family: $observ-font-family;
472
+ font-style: normal;
473
+ font-weight: 400;
474
+ text-transform: none;
475
+ letter-spacing: normal;
476
+ }
477
+
478
+ &:disabled {
479
+ background-color: $observ-bg-surface;
480
+ color: $observ-text-muted;
481
+ cursor: not-allowed;
482
+ }
483
+ }
484
+
485
+ &__textarea {
486
+ resize: vertical;
487
+ min-height: 80px;
488
+
489
+ &--code {
490
+ font-family: $observ-font-family-mono;
491
+ font-size: $observ-font-size-sm;
492
+
493
+ // Code textarea placeholder should also use mono font
494
+ &::placeholder {
495
+ font-family: $observ-font-family-mono;
496
+ }
497
+
498
+ &::-webkit-input-placeholder {
499
+ font-family: $observ-font-family-mono;
500
+ }
501
+
502
+ &::-moz-placeholder {
503
+ font-family: $observ-font-family-mono;
504
+ }
505
+
506
+ &:-ms-input-placeholder {
507
+ font-family: $observ-font-family-mono;
508
+ }
509
+ }
510
+ }
511
+
512
+ &__select {
513
+ cursor: pointer;
514
+ appearance: none;
515
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%239ca3af' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
516
+ background-repeat: no-repeat;
517
+ background-position: right $observ-spacing-md center;
518
+ padding-right: calc($observ-spacing-md + 1.5rem);
519
+ }
520
+
521
+ &__hint {
522
+ margin-top: $observ-spacing-xs;
523
+ font-size: $observ-font-size-sm;
524
+ color: $observ-text-muted;
525
+
526
+ &--warning {
527
+ color: lighten($observ-warning, 5%);
528
+ }
529
+ }
530
+
531
+ &__actions {
532
+ display: flex;
533
+ align-items: center;
534
+ gap: $observ-spacing-md;
535
+ padding-top: $observ-spacing-lg;
536
+ border-top: 1px solid $observ-border-subtle;
537
+ margin-top: $observ-spacing-lg;
538
+ }
539
+
540
+ &__checkbox-group {
541
+ display: flex;
542
+ flex-direction: column;
543
+ gap: $observ-spacing-xs;
544
+ }
545
+
546
+ &__checkbox-label {
547
+ display: inline-flex;
548
+ align-items: center;
549
+ gap: $observ-spacing-sm;
550
+ cursor: pointer;
551
+ user-select: none;
552
+
553
+ &:hover .observ-form__checkbox-custom {
554
+ border-color: $observ-primary;
555
+ }
556
+ }
557
+
558
+ &__checkbox {
559
+ // Hide the native checkbox but keep it accessible
560
+ position: absolute;
561
+ opacity: 0;
562
+ width: 0;
563
+ height: 0;
564
+
565
+ // When checked, style the custom checkbox
566
+ &:checked + .observ-form__checkbox-custom {
567
+ background-color: $observ-primary;
568
+ border-color: $observ-primary;
569
+
570
+ &::after {
571
+ opacity: 1;
572
+ transform: rotate(45deg) scale(1);
573
+ }
574
+ }
575
+
576
+ // Focus state
577
+ &:focus + .observ-form__checkbox-custom {
578
+ box-shadow: 0 0 0 3px rgba($observ-primary, 0.2);
579
+ }
580
+ }
581
+
582
+ &__checkbox-custom {
583
+ position: relative;
584
+ width: 1.125rem;
585
+ height: 1.125rem;
586
+ background-color: $observ-bg-elevated;
587
+ border: 2px solid $observ-border-strong;
588
+ border-radius: $observ-border-radius-sm;
589
+ flex-shrink: 0;
590
+ transition: all 0.15s ease;
591
+
592
+ // Checkmark
593
+ &::after {
594
+ content: '';
595
+ position: absolute;
596
+ left: 5px;
597
+ top: 2px;
598
+ width: 5px;
599
+ height: 9px;
600
+ border: solid $observ-white;
601
+ border-width: 0 2px 2px 0;
602
+ opacity: 0;
603
+ transform: rotate(45deg) scale(0.5);
604
+ transition: all 0.15s ease;
605
+ }
606
+ }
607
+
608
+ &__checkbox-text {
609
+ font-family: $observ-font-family;
610
+ font-size: $observ-font-size-sm;
611
+ color: $observ-text-primary;
612
+ }
613
+
614
+ &__checkbox-hint {
615
+ margin-left: calc(1.125rem + #{$observ-spacing-sm});
616
+ font-family: $observ-font-family;
617
+ font-size: $observ-font-size-xs;
618
+ color: $observ-text-muted;
619
+ }
424
620
  }
425
- }
426
621
 
427
- .observ-tabs {
428
- margin-bottom: $observ-spacing-lg;
622
+ .observ-alert__title {
623
+ margin: 0 0 $observ-spacing-sm 0;
624
+ font-size: $observ-font-size-base;
625
+ font-weight: 600;
626
+ }
429
627
 
430
- &__list {
628
+ .observ-alert__list {
629
+ margin: 0;
630
+ padding-left: $observ-spacing-lg;
631
+ }
632
+
633
+ .observ-form-separator {
634
+ border-top: 1px solid $observ-border-subtle;
635
+ padding-top: $observ-spacing-lg;
636
+ margin-top: $observ-spacing-lg;
637
+ }
638
+
639
+ .observ-form-actions {
431
640
  display: flex;
641
+ align-items: center;
642
+ gap: $observ-spacing-md;
643
+ }
644
+
645
+ .observ-loading-indicator {
646
+ display: inline-flex;
647
+ align-items: center;
432
648
  gap: $observ-spacing-sm;
433
- border-bottom: 2px solid $observ-gray-200;
649
+ color: $observ-text-secondary;
650
+ font-size: $observ-font-size-sm;
434
651
  }
435
652
 
436
- &__tab {
437
- padding: $observ-spacing-sm $observ-spacing-lg;
438
- color: $observ-gray-600;
653
+ .observ-spinner {
654
+ display: inline-block;
655
+ width: 1rem;
656
+ height: 1rem;
657
+ border: 2px solid $observ-border-color;
658
+ border-top-color: $observ-primary;
659
+ border-radius: 50%;
660
+ animation: observ-spin 0.8s linear infinite;
661
+ }
662
+
663
+ .observ-tab {
664
+ display: inline-block;
665
+ padding: $observ-spacing-sm $observ-spacing-md;
666
+ color: $observ-text-secondary;
439
667
  text-decoration: none;
440
668
  font-weight: 500;
441
669
  border-bottom: 2px solid transparent;
442
- margin-bottom: -2px;
443
670
  transition: $observ-transition;
444
671
 
445
672
  &:hover {
446
- color: $observ-gray-900;
447
- border-bottom-color: $observ-gray-300;
673
+ color: $observ-text-primary;
674
+ text-decoration: none;
448
675
  }
449
676
 
450
677
  &--active {
@@ -453,8 +680,63 @@ select.observ-input,
453
680
 
454
681
  &:hover {
455
682
  color: $observ-primary;
456
- border-bottom-color: $observ-primary;
457
683
  }
458
684
  }
459
685
  }
686
+
687
+ .observ-tabs {
688
+ display: flex;
689
+ gap: $observ-spacing-sm;
690
+ border-bottom: 2px solid $observ-border-subtle;
691
+ margin-bottom: $observ-spacing-lg;
692
+ }
693
+
694
+ // Stat list for displaying key-value pairs with good spacing
695
+ .observ-stat-list {
696
+ display: flex;
697
+ flex-direction: column;
698
+ gap: $observ-spacing-sm;
699
+
700
+ &__item {
701
+ display: flex;
702
+ align-items: center;
703
+ justify-content: space-between;
704
+ padding: $observ-spacing-sm $observ-spacing-md;
705
+ background-color: $observ-bg-elevated;
706
+ border-radius: $observ-border-radius-sm;
707
+ border: 1px solid transparent;
708
+ transition: $observ-transition;
709
+
710
+ &:hover {
711
+ border-color: $observ-border-subtle;
712
+ }
713
+ }
714
+
715
+ &__label {
716
+ font-size: $observ-font-size-sm;
717
+ color: $observ-text-secondary;
718
+ }
719
+
720
+ &__value {
721
+ font-size: $observ-font-size-sm;
722
+ font-weight: 600;
723
+ color: $observ-text-primary;
724
+ font-variant-numeric: tabular-nums;
725
+ }
726
+
727
+ // Variant with larger spacing
728
+ &--spacious {
729
+ gap: $observ-spacing-md;
730
+
731
+ .observ-stat-list__item {
732
+ padding: $observ-spacing-md $observ-spacing-lg;
733
+ }
734
+ }
735
+ }
736
+ }
737
+
738
+ @keyframes observ-spin {
739
+ to {
740
+ transform: rotate(360deg);
741
+ }
460
742
  }