rubyllm-observ 0.5.0 → 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 (96) 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. data/lib/rubyllm-observ.rb +1 -0
  96. metadata +31 -1
@@ -1,273 +1,295 @@
1
1
  @import 'variables';
2
+ @import 'namespace';
3
+
4
+ @include observ-scoped {
5
+ .observ-drawer {
6
+ position: fixed;
7
+ top: 0;
8
+ left: 0;
9
+ right: 0;
10
+ bottom: 0;
11
+ z-index: 1000;
12
+ pointer-events: none;
13
+
14
+ &.open {
15
+ pointer-events: auto;
16
+
17
+ .observ-drawer__overlay {
18
+ opacity: 1;
19
+ }
2
20
 
3
- .observ-drawer {
4
- position: fixed;
5
- top: 0;
6
- left: 0;
7
- right: 0;
8
- bottom: 0;
9
- z-index: 1000;
10
- pointer-events: none;
11
-
12
- &.open {
13
- pointer-events: auto;
14
-
15
- .observ-drawer__overlay {
16
- opacity: 1;
21
+ .observ-drawer__panel {
22
+ transform: translateX(0);
23
+ }
17
24
  }
25
+ }
18
26
 
19
- .observ-drawer__panel {
20
- transform: translateX(0);
21
- }
27
+ .observ-drawer__overlay {
28
+ position: absolute;
29
+ top: 0;
30
+ left: 0;
31
+ right: 0;
32
+ bottom: 0;
33
+ background-color: rgba($observ-black, 0.7);
34
+ opacity: 0;
35
+ transition: opacity 0.3s ease-in-out;
36
+ cursor: pointer;
22
37
  }
23
- }
24
38
 
25
- .observ-drawer__overlay {
26
- position: absolute;
27
- top: 0;
28
- left: 0;
29
- right: 0;
30
- bottom: 0;
31
- background-color: rgba($observ-black, 0.5);
32
- opacity: 0;
33
- transition: opacity 0.3s ease-in-out;
34
- cursor: pointer;
35
- }
39
+ .observ-drawer__panel {
40
+ position: absolute;
41
+ top: 0;
42
+ right: 0;
43
+ bottom: 0;
44
+ width: 100%;
45
+ max-width: 600px;
46
+ background-color: $observ-bg-surface;
47
+ border-left: 1px solid $observ-border-color;
48
+ transform: translateX(100%);
49
+ transition: transform 0.3s ease-in-out;
50
+ display: flex;
51
+ flex-direction: column;
52
+ overflow: hidden;
53
+
54
+ @media (max-width: $observ-breakpoint-md) {
55
+ max-width: 90%;
56
+ }
57
+ }
36
58
 
37
- .observ-drawer__panel {
38
- position: absolute;
39
- top: 0;
40
- right: 0;
41
- bottom: 0;
42
- width: 100%;
43
- max-width: 600px;
44
- background-color: $observ-white;
45
- box-shadow: $observ-shadow-lg;
46
- transform: translateX(100%);
47
- transition: transform 0.3s ease-in-out;
48
- display: flex;
49
- flex-direction: column;
50
- overflow: hidden;
51
-
52
- @media (max-width: $observ-breakpoint-md) {
53
- max-width: 90%;
59
+ .observ-drawer__header {
60
+ display: flex;
61
+ align-items: center;
62
+ gap: $observ-spacing-md;
63
+ padding: $observ-spacing-lg;
64
+ border-bottom: 1px solid $observ-border-subtle;
65
+ background-color: $observ-bg-surface;
66
+ flex-shrink: 0;
54
67
  }
55
- }
56
68
 
57
- .observ-drawer__header {
58
- display: flex;
59
- align-items: center;
60
- gap: $observ-spacing-md;
61
- padding: $observ-spacing-lg;
62
- border-bottom: 1px solid $observ-gray-200;
63
- background-color: $observ-white;
64
- flex-shrink: 0;
65
- }
69
+ .observ-drawer__close-btn {
70
+ display: flex;
71
+ align-items: center;
72
+ justify-content: center;
73
+ width: 2rem;
74
+ height: 2rem;
75
+ padding: 0;
76
+ border: none;
77
+ background: none;
78
+ color: $observ-text-secondary;
79
+ cursor: pointer;
80
+ border-radius: $observ-border-radius-sm;
81
+ transition: $observ-transition;
82
+ flex-shrink: 0;
66
83
 
67
- .observ-drawer__close-btn {
68
- display: flex;
69
- align-items: center;
70
- justify-content: center;
71
- width: 2rem;
72
- height: 2rem;
73
- padding: 0;
74
- border: none;
75
- background: none;
76
- color: $observ-gray-600;
77
- cursor: pointer;
78
- border-radius: $observ-border-radius-sm;
79
- transition: $observ-transition;
80
- flex-shrink: 0;
81
-
82
- &:hover {
83
- background-color: $observ-gray-100;
84
- color: $observ-gray-900;
85
- }
84
+ &:hover {
85
+ background-color: $observ-bg-hover;
86
+ color: $observ-text-primary;
87
+ }
86
88
 
87
- &:active {
88
- background-color: $observ-gray-200;
89
- }
89
+ &:active {
90
+ background-color: $observ-bg-elevated;
91
+ }
90
92
 
91
- svg {
92
- display: block;
93
+ svg {
94
+ display: block;
95
+ }
93
96
  }
94
- }
95
97
 
96
- .observ-drawer__title {
97
- margin: 0;
98
- font-size: $observ-font-size-xl;
99
- font-weight: 600;
100
- color: $observ-gray-900;
101
- flex: 1;
102
- min-width: 0;
103
-
104
- .observ-spinner {
105
- width: 1.25rem;
106
- height: 1.25rem;
98
+ .observ-drawer__title {
99
+ margin: 0;
100
+ font-size: $observ-font-size-xl;
101
+ font-weight: 600;
102
+ color: $observ-text-primary;
103
+ flex: 1;
104
+ min-width: 0;
105
+
106
+ .observ-spinner {
107
+ width: 1.25rem;
108
+ height: 1.25rem;
109
+ }
107
110
  }
108
- }
109
111
 
110
- .observ-drawer__content {
111
- flex: 1;
112
- overflow-y: auto;
113
- padding: $observ-spacing-lg;
114
- background-color: $observ-white;
115
- }
112
+ .observ-drawer__content {
113
+ flex: 1;
114
+ overflow-y: auto;
115
+ padding: $observ-spacing-lg;
116
+ background-color: $observ-bg-surface;
117
+ }
116
118
 
117
- .observ-drawer__loading {
118
- display: flex;
119
- align-items: center;
120
- justify-content: center;
121
- padding: $observ-spacing-2xl;
122
- color: $observ-gray-500;
119
+ .observ-drawer__loading {
120
+ display: flex;
121
+ align-items: center;
122
+ justify-content: center;
123
+ padding: $observ-spacing-2xl;
124
+ color: $observ-text-muted;
123
125
 
124
- .observ-spinner {
125
- width: 2rem;
126
- height: 2rem;
126
+ .observ-spinner {
127
+ width: 2rem;
128
+ height: 2rem;
129
+ }
127
130
  }
128
- }
129
131
 
130
- .observ-drawer__error {
131
- padding: $observ-spacing-lg;
132
- background-color: lighten($observ-danger, 45%);
133
- color: darken($observ-danger, 10%);
134
- border-radius: $observ-border-radius;
135
- text-align: center;
136
- }
132
+ .observ-drawer__error {
133
+ padding: $observ-spacing-lg;
134
+ background-color: rgba($observ-danger, 0.1);
135
+ color: lighten($observ-danger, 15%);
136
+ border-radius: $observ-border-radius;
137
+ text-align: center;
138
+ }
137
139
 
138
- // Text output specific styles
139
- .observ-drawer__text-output {
140
- position: relative;
141
- width: 100%;
142
- }
140
+ // Text output specific styles
141
+ .observ-drawer__text-output {
142
+ position: relative;
143
+ width: 100%;
144
+ }
143
145
 
144
- .observ-drawer__textarea {
145
- width: 100%;
146
- min-height: 600px;
147
- padding: $observ-spacing-md;
148
- border: 1px solid $observ-gray-300;
149
- border-radius: $observ-border-radius;
150
- font-family: $observ-font-family-mono;
151
- font-size: 12px;
152
- line-height: 1.6;
153
- resize: vertical;
154
- background-color: $observ-gray-50;
155
- color: $observ-gray-900;
156
-
157
- &:focus {
158
- outline: none;
159
- border-color: $observ-primary;
160
- box-shadow: 0 0 0 3px rgba($observ-primary, 0.1);
146
+ .observ-drawer__textarea {
147
+ width: 100%;
148
+ min-height: 600px;
149
+ padding: $observ-spacing-md;
150
+ border: 1px solid $observ-border-color;
151
+ border-radius: $observ-border-radius;
152
+ font-family: $observ-font-family-mono;
153
+ font-size: 12px;
154
+ line-height: 1.6;
155
+ resize: vertical;
156
+ background-color: $observ-bg-elevated;
157
+ color: $observ-text-primary;
158
+
159
+ &:focus {
160
+ outline: none;
161
+ border-color: $observ-primary;
162
+ box-shadow: 0 0 0 3px rgba($observ-primary, 0.1);
163
+ }
161
164
  }
162
- }
163
165
 
164
- .observ-drawer__copy-btn {
165
- position: absolute;
166
- top: $observ-spacing-sm;
167
- right: $observ-spacing-sm;
168
- }
166
+ .observ-drawer__copy-btn {
167
+ position: absolute;
168
+ top: $observ-spacing-sm;
169
+ right: $observ-spacing-sm;
170
+ }
169
171
 
170
- .observ-drawer__instructions {
171
- margin-bottom: $observ-spacing-md;
172
- padding: $observ-spacing-sm;
173
- background-color: lighten($observ-info, 45%);
174
- border-left: 3px solid $observ-info;
175
- border-radius: $observ-border-radius-sm;
176
- font-size: $observ-font-size-sm;
177
- color: $observ-gray-700;
178
- }
172
+ .observ-drawer__instructions {
173
+ margin-bottom: $observ-spacing-md;
174
+ padding: $observ-spacing-sm;
175
+ background-color: rgba($observ-info, 0.1);
176
+ border-left: 3px solid $observ-info;
177
+ border-radius: $observ-border-radius-sm;
178
+ font-size: $observ-font-size-sm;
179
+ color: lighten($observ-info, 15%);
180
+ }
179
181
 
180
- .observ-drawer__help {
181
- margin-top: $observ-spacing-md;
182
-
183
- details {
184
- cursor: pointer;
182
+ .observ-drawer__help {
183
+ margin-top: $observ-spacing-md;
184
+
185
+ details {
186
+ cursor: pointer;
187
+
188
+ summary {
189
+ color: $observ-text-secondary;
190
+ font-size: $observ-font-size-sm;
191
+ font-weight: 500;
192
+ padding: $observ-spacing-xs;
193
+
194
+ &:hover {
195
+ color: $observ-text-primary;
196
+ }
197
+ }
198
+ }
185
199
 
186
- summary {
187
- color: $observ-gray-600;
200
+ &-list {
201
+ margin-top: $observ-spacing-sm;
202
+ padding-left: $observ-spacing-lg;
188
203
  font-size: $observ-font-size-sm;
189
- font-weight: 500;
190
- padding: $observ-spacing-xs;
204
+ color: $observ-text-secondary;
191
205
 
192
- &:hover {
193
- color: $observ-gray-900;
206
+ li {
207
+ margin-bottom: $observ-spacing-xs;
194
208
  }
195
209
  }
196
210
  }
197
-
198
- &-list {
199
- margin-top: $observ-spacing-sm;
200
- padding-left: $observ-spacing-lg;
201
- font-size: $observ-font-size-sm;
202
- color: $observ-gray-600;
203
-
204
- li {
205
- margin-bottom: $observ-spacing-xs;
211
+
212
+ // Field styles for structured content display
213
+ .observ-drawer__field {
214
+ margin-bottom: $observ-spacing-lg;
215
+
216
+ &:last-child {
217
+ margin-bottom: 0;
206
218
  }
207
219
  }
208
- }
209
220
 
210
- // Field styles for structured content display
211
- .observ-drawer__field {
212
- margin-bottom: $observ-spacing-lg;
221
+ .observ-drawer__field-label {
222
+ display: block;
223
+ margin-bottom: $observ-spacing-xs;
224
+ font-size: $observ-font-size-sm;
225
+ font-weight: 600;
226
+ color: $observ-text-secondary;
227
+ text-transform: uppercase;
228
+ letter-spacing: 0.025em;
229
+ }
213
230
 
214
- &:last-child {
215
- margin-bottom: 0;
231
+ .observ-drawer__field-value {
232
+ display: flex;
233
+ align-items: center;
234
+ gap: $observ-spacing-sm;
216
235
  }
217
- }
218
236
 
219
- .observ-drawer__field-label {
220
- display: block;
221
- margin-bottom: $observ-spacing-xs;
222
- font-size: $observ-font-size-sm;
223
- font-weight: 600;
224
- color: $observ-gray-700;
225
- text-transform: uppercase;
226
- letter-spacing: 0.025em;
227
- }
237
+ // Metrics grid for displaying stats
238
+ .observ-drawer__metrics {
239
+ display: grid;
240
+ grid-template-columns: repeat(3, 1fr);
241
+ gap: $observ-spacing-md;
242
+ margin-bottom: $observ-spacing-lg;
243
+ padding: $observ-spacing-md;
244
+ background-color: $observ-bg-elevated;
245
+ border: 1px solid $observ-border-subtle;
246
+ border-radius: $observ-border-radius;
247
+ }
228
248
 
229
- .observ-drawer__field-value {
230
- display: flex;
231
- align-items: center;
232
- gap: $observ-spacing-sm;
233
- }
249
+ .observ-drawer__metric {
250
+ text-align: center;
251
+ }
234
252
 
235
- // Metrics grid for displaying stats
236
- .observ-drawer__metrics {
237
- display: grid;
238
- grid-template-columns: repeat(3, 1fr);
239
- gap: $observ-spacing-md;
240
- margin-bottom: $observ-spacing-lg;
241
- padding: $observ-spacing-md;
242
- background-color: $observ-gray-50;
243
- border-radius: $observ-border-radius;
244
- }
253
+ .observ-drawer__metric-label {
254
+ display: block;
255
+ font-size: $observ-font-size-xs;
256
+ color: $observ-text-muted;
257
+ text-transform: uppercase;
258
+ letter-spacing: 0.05em;
259
+ margin-bottom: $observ-spacing-xs;
260
+ }
245
261
 
246
- .observ-drawer__metric {
247
- text-align: center;
248
- }
262
+ .observ-drawer__metric-value {
263
+ display: block;
264
+ font-size: $observ-font-size-lg;
265
+ font-weight: 600;
266
+ color: $observ-text-primary;
267
+ }
249
268
 
250
- .observ-drawer__metric-label {
251
- display: block;
252
- font-size: $observ-font-size-xs;
253
- color: $observ-gray-500;
254
- text-transform: uppercase;
255
- letter-spacing: 0.05em;
256
- margin-bottom: $observ-spacing-xs;
257
- }
269
+ // Actions footer
270
+ .observ-drawer__actions {
271
+ display: flex;
272
+ gap: $observ-spacing-sm;
273
+ margin-top: $observ-spacing-lg;
274
+ padding-top: $observ-spacing-lg;
275
+ border-top: 1px solid $observ-border-subtle;
276
+ }
258
277
 
259
- .observ-drawer__metric-value {
260
- display: block;
261
- font-size: $observ-font-size-lg;
262
- font-weight: 600;
263
- color: $observ-gray-900;
264
- }
278
+ .observ-drawer__section {
279
+ padding: $observ-spacing-lg;
280
+ border-bottom: 1px solid $observ-border-subtle;
265
281
 
266
- // Actions footer
267
- .observ-drawer__actions {
268
- display: flex;
269
- gap: $observ-spacing-sm;
270
- margin-top: $observ-spacing-lg;
271
- padding-top: $observ-spacing-lg;
272
- border-top: 1px solid $observ-gray-200;
282
+ &:last-child {
283
+ border-bottom: none;
284
+ }
285
+ }
286
+
287
+ .observ-drawer__section-title {
288
+ font-size: $observ-font-size-sm;
289
+ font-weight: 600;
290
+ color: $observ-text-muted;
291
+ text-transform: uppercase;
292
+ letter-spacing: 0.05em;
293
+ margin: 0 0 $observ-spacing-md;
294
+ }
273
295
  }
@@ -0,0 +1,139 @@
1
+ @import 'variables';
2
+ @import 'namespace';
3
+
4
+ // Shared Filter Component
5
+ // Usage: Apply .observ-filters to a card section, then use BEM children
6
+ //
7
+ // Example:
8
+ // <section class="observ-card observ-filters">
9
+ // <div class="observ-card__body">
10
+ // <%= form_with class: "observ-filters__form" do |f| %>
11
+ // <div class="observ-filters__field observ-filters__field--flex">
12
+ // <%= f.label :search, class: "observ-filters__label" %>
13
+ // <%= f.text_field :search, class: "observ-filters__input" %>
14
+ // </div>
15
+ // <div class="observ-filters__field observ-filters__field--md">
16
+ // <%= f.label :status, class: "observ-filters__label" %>
17
+ // <%= f.select :status, options, class: "observ-filters__select" %>
18
+ // </div>
19
+ // <div class="observ-filters__actions">
20
+ // <%= f.submit "Filter", class: "observ-button observ-button--secondary" %>
21
+ // <%= link_to "Clear", path, class: "observ-button" %>
22
+ // </div>
23
+ // <% end %>
24
+ // </div>
25
+ // </section>
26
+
27
+ @include observ-scoped {
28
+ .observ-filters {
29
+ margin-bottom: $observ-spacing-lg;
30
+
31
+ &__form {
32
+ display: flex;
33
+ gap: $observ-spacing-md;
34
+ align-items: flex-end;
35
+ flex-wrap: wrap;
36
+ }
37
+
38
+ &__field {
39
+ display: flex;
40
+ flex-direction: column;
41
+
42
+ // Flexible field (takes remaining space)
43
+ &--flex {
44
+ flex: 1;
45
+ min-width: 150px;
46
+ }
47
+
48
+ // Fixed width modifiers
49
+ &--sm {
50
+ width: 8rem;
51
+ }
52
+
53
+ &--md {
54
+ width: 10rem;
55
+ }
56
+
57
+ &--lg {
58
+ width: 12rem;
59
+ }
60
+ }
61
+
62
+ &__label {
63
+ display: block;
64
+ margin-bottom: $observ-spacing-xs;
65
+ font-weight: 500;
66
+ color: $observ-text-secondary;
67
+ font-size: $observ-font-size-sm;
68
+ }
69
+
70
+ &__input,
71
+ &__select {
72
+ width: 100%;
73
+ padding: $observ-spacing-sm;
74
+ border: 1px solid $observ-border-color;
75
+ border-radius: $observ-border-radius-sm;
76
+ font-size: $observ-font-size-base;
77
+ font-family: inherit;
78
+ color: $observ-text-primary;
79
+ background-color: $observ-bg-elevated;
80
+ transition: border-color 0.2s ease, box-shadow 0.2s ease;
81
+
82
+ &:hover {
83
+ border-color: $observ-border-strong;
84
+ }
85
+
86
+ &:focus {
87
+ outline: none;
88
+ border-color: $observ-primary;
89
+ box-shadow: 0 0 0 3px rgba($observ-primary, 0.1);
90
+ }
91
+
92
+ &::placeholder {
93
+ color: $observ-text-muted;
94
+ }
95
+
96
+ &:disabled {
97
+ background-color: $observ-bg-surface;
98
+ color: $observ-text-muted;
99
+ cursor: not-allowed;
100
+ }
101
+ }
102
+
103
+ &__select {
104
+ appearance: none;
105
+ -webkit-appearance: none;
106
+ -moz-appearance: none;
107
+ cursor: pointer;
108
+ 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");
109
+ background-repeat: no-repeat;
110
+ background-position: right $observ-spacing-sm center;
111
+ padding-right: calc(#{$observ-spacing-sm} + 1.5rem);
112
+
113
+ option {
114
+ background-color: $observ-bg-elevated;
115
+ color: $observ-text-primary;
116
+ }
117
+ }
118
+
119
+ // Date inputs
120
+ &__input[type="date"],
121
+ &__input[type="datetime-local"] {
122
+ &::-webkit-calendar-picker-indicator {
123
+ cursor: pointer;
124
+ filter: invert(0.7) brightness(1.5);
125
+ margin-left: $observ-spacing-md;
126
+ transition: filter 0.2s ease;
127
+
128
+ &:hover {
129
+ filter: invert(0.9) brightness(1.8);
130
+ }
131
+ }
132
+ }
133
+
134
+ &__actions {
135
+ display: flex;
136
+ gap: $observ-spacing-sm;
137
+ }
138
+ }
139
+ }