athar 0.2.1 → 0.3.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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -1
  3. data/README.md +60 -0
  4. data/app/assets/images/athar/logo.png +0 -0
  5. data/app/assets/javascripts/athar/dashboard.js +290 -0
  6. data/app/assets/stylesheets/athar/dashboard.css +841 -0
  7. data/app/controllers/athar/application_controller.rb +10 -0
  8. data/app/controllers/athar/dashboard_controller.rb +57 -0
  9. data/app/controllers/athar/deletions_controller.rb +14 -0
  10. data/app/controllers/athar/table_events_controller.rb +11 -0
  11. data/app/controllers/athar/themes_controller.rb +16 -0
  12. data/app/helpers/athar/asset_helper.rb +28 -0
  13. data/app/helpers/athar/dashboard/cell_helper.rb +88 -0
  14. data/app/helpers/athar/dashboard/detail_helper.rb +50 -0
  15. data/app/helpers/athar/dashboard/filter_link_helper.rb +22 -0
  16. data/app/helpers/athar/dashboard/formatting_helper.rb +47 -0
  17. data/app/helpers/athar/dashboard/icon_helper.rb +40 -0
  18. data/app/helpers/athar/dashboard_helper.rb +11 -0
  19. data/app/views/athar/dashboard/_filter_bar.html.erb +95 -0
  20. data/app/views/athar/dashboard/_kpi_strip.html.erb +46 -0
  21. data/app/views/athar/dashboard/_pager.html.erb +32 -0
  22. data/app/views/athar/dashboard/_row.html.erb +72 -0
  23. data/app/views/athar/dashboard/_sidebar.html.erb +106 -0
  24. data/app/views/athar/dashboard/_table.html.erb +30 -0
  25. data/app/views/athar/dashboard/_topbar.html.erb +30 -0
  26. data/app/views/athar/dashboard/index.html.erb +31 -0
  27. data/app/views/athar/deletions/_detail.html.erb +115 -0
  28. data/app/views/athar/deletions/show.html.erb +3 -0
  29. data/app/views/athar/table_events/_detail.html.erb +80 -0
  30. data/app/views/athar/table_events/show.html.erb +3 -0
  31. data/app/views/layouts/athar/application.html.erb +29 -0
  32. data/config/routes.rb +8 -0
  33. data/lib/athar/dashboard/actor_labels.rb +31 -0
  34. data/lib/athar/dashboard/actor_options.rb +71 -0
  35. data/lib/athar/dashboard/connection_info.rb +25 -0
  36. data/lib/athar/dashboard/feed_query.rb +222 -0
  37. data/lib/athar/dashboard/filter_set.rb +63 -0
  38. data/lib/athar/dashboard/kpi_calculator.rb +102 -0
  39. data/lib/athar/dashboard/model_registry.rb +141 -0
  40. data/lib/athar/dashboard/sparkline.rb +42 -0
  41. data/lib/athar/dashboard/trigger_args_parser.rb +42 -0
  42. data/lib/athar/dashboard.rb +16 -0
  43. data/lib/athar/engine.rb +12 -0
  44. data/lib/athar/middleware/asset_server.rb +78 -0
  45. data/lib/athar/version.rb +1 -1
  46. data/lib/athar.rb +1 -0
  47. metadata +41 -1
@@ -0,0 +1,841 @@
1
+ /* ===== tokens ===== */
2
+ :root[data-theme="dark"] {
3
+ --bg: #0c0d10;
4
+ --bg-1: #111317;
5
+ --bg-2: #15181d;
6
+ --bg-3: #1a1e24;
7
+ --bg-row-hover: #16191f;
8
+ --bg-row-open: #14171c;
9
+ --bg-detail: #0e1014;
10
+ --border: #20242c;
11
+ --border-strong: #2a2f38;
12
+ --fg: #e6e9ef;
13
+ --fg-1: #c2c8d2;
14
+ --fg-2: #8a92a0;
15
+ --fg-3: #5d6573;
16
+ --fg-4: #3f4651;
17
+ --accent: #d6deea;
18
+ --selected: #1d2530;
19
+ --selected-bar: #2c3a52;
20
+ --mode-identity: #6b7384;
21
+ --mode-only: #6694e8;
22
+ --mode-snapshot: #c98a3f;
23
+ --mask: #c98a3f;
24
+ --truncate: #d96565;
25
+ --kind-delete: #6b7384;
26
+ --kind-truncate: #d96565;
27
+ --conn: #4ea36b;
28
+ --shadow: 0 1px 0 0 rgba(255,255,255,0.02), 0 0 0 1px rgba(0,0,0,0.6);
29
+ }
30
+
31
+ :root[data-theme="light"] {
32
+ --bg: #f5f6f8;
33
+ --bg-1: #ffffff;
34
+ --bg-2: #fafbfc;
35
+ --bg-3: #eef0f4;
36
+ --bg-row-hover: #f3f5f8;
37
+ --bg-row-open: #f3f5f8;
38
+ --bg-detail: #f9fafc;
39
+ --border: #e3e6eb;
40
+ --border-strong: #cdd2da;
41
+ --fg: #0f1216;
42
+ --fg-1: #2a2f38;
43
+ --fg-2: #5b6471;
44
+ --fg-3: #8a92a0;
45
+ --fg-4: #b6bcc7;
46
+ --accent: #1a1d22;
47
+ --selected: #e7eef9;
48
+ --selected-bar: #c5d4ee;
49
+ --mode-identity: #6b7384;
50
+ --mode-only: #2563d4;
51
+ --mode-snapshot: #b16a1f;
52
+ --mask: #b16a1f;
53
+ --truncate: #c43a3a;
54
+ --kind-delete: #6b7384;
55
+ --kind-truncate: #c43a3a;
56
+ --conn: #2f8a52;
57
+ --shadow: 0 1px 0 0 rgba(0,0,0,0.02);
58
+ }
59
+
60
+ * { box-sizing: border-box; }
61
+ html, body { height: 100%; margin: 0; }
62
+ body {
63
+ font-family: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
64
+ font-size: 12.5px;
65
+ line-height: 1.4;
66
+ color: var(--fg);
67
+ background: var(--bg);
68
+ -webkit-font-smoothing: antialiased;
69
+ font-feature-settings: "cv11", "ss01";
70
+ }
71
+ .mono, code, pre, input, select, .kbd { font-family: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace; }
72
+ button { font-family: inherit; cursor: pointer; }
73
+
74
+ /* ===== layout ===== */
75
+ .app {
76
+ display: grid;
77
+ grid-template-columns: 264px 1fr;
78
+ height: 100vh;
79
+ overflow: hidden;
80
+ }
81
+
82
+ /* ===== sidebar ===== */
83
+ .sidebar {
84
+ background: var(--bg-1);
85
+ border-right: 1px solid var(--border);
86
+ display: flex;
87
+ flex-direction: column;
88
+ min-width: 0;
89
+ }
90
+ .sidebar-head {
91
+ padding: 12px 12px 10px;
92
+ border-bottom: 1px solid var(--border);
93
+ display: flex;
94
+ flex-direction: column;
95
+ gap: 10px;
96
+ }
97
+ .kbd-row { display: flex; align-items: center; justify-content: space-between; }
98
+ .brand { display: flex; align-items: center; gap: 6px; }
99
+ .brand-name { line-height: 1; }
100
+ .brand-version { line-height: 1; }
101
+ .brand-logo {
102
+ width: 18px;
103
+ height: 18px;
104
+ border-radius: 4px;
105
+ margin-right: 4px;
106
+ display: block;
107
+ flex-shrink: 0;
108
+ }
109
+ :root[data-theme="light"] .brand-logo {
110
+ background: #0c0d10;
111
+ padding: 1px;
112
+ }
113
+ .brand-name { font-weight: 600; letter-spacing: -0.01em; color: var(--fg); }
114
+ .brand-version { font-family: "JetBrains Mono", monospace; font-size: 10px; color: var(--fg-3); }
115
+
116
+ .sidebar-search {
117
+ display: flex; align-items: center; gap: 6px;
118
+ background: var(--bg-2);
119
+ border: 1px solid var(--border);
120
+ border-radius: 4px;
121
+ padding: 4px 6px;
122
+ color: var(--fg-3);
123
+ }
124
+ .sidebar-search input {
125
+ flex: 1; min-width: 0;
126
+ background: transparent;
127
+ border: 0; outline: 0;
128
+ color: var(--fg);
129
+ font-size: 11.5px;
130
+ }
131
+ .kbd {
132
+ display: inline-flex; align-items: center; justify-content: center;
133
+ height: 16px; min-width: 14px; padding: 0 3px;
134
+ border: 1px solid var(--border);
135
+ border-radius: 3px;
136
+ font-size: 10px;
137
+ color: var(--fg-3);
138
+ background: var(--bg-3);
139
+ }
140
+
141
+ .sidebar-all {
142
+ margin: 6px 6px 0;
143
+ border-radius: 4px;
144
+ }
145
+ .sidebar-scroll { flex: 1; overflow-y: auto; padding: 4px 0 8px; }
146
+ .sidebar-scroll::-webkit-scrollbar { width: 8px; }
147
+ .sidebar-scroll::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 4px; }
148
+ .sidebar-scroll::-webkit-scrollbar-track { background: transparent; }
149
+
150
+ .sidebar-group { margin-top: 10px; }
151
+ .sidebar-group-head {
152
+ display: flex; justify-content: space-between; align-items: center;
153
+ padding: 4px 14px 4px;
154
+ font-size: 10px;
155
+ text-transform: uppercase;
156
+ letter-spacing: 0.06em;
157
+ color: var(--fg-3);
158
+ }
159
+ .sidebar-group-count { color: var(--fg-4); font-family: "JetBrains Mono", monospace; }
160
+
161
+ .sidebar-row {
162
+ position: relative;
163
+ display: flex;
164
+ align-items: center;
165
+ justify-content: space-between;
166
+ width: calc(100% - 12px);
167
+ margin: 0 6px;
168
+ padding: 5px 8px;
169
+ border: 0;
170
+ background: transparent;
171
+ color: var(--fg-1);
172
+ border-radius: 3px;
173
+ text-align: left;
174
+ font-size: 12px;
175
+ overflow: hidden;
176
+ }
177
+ .sidebar-row:hover { background: var(--bg-row-hover); }
178
+ .sidebar-row.is-active { background: var(--selected); color: var(--fg); }
179
+ .sidebar-row.is-active::before {
180
+ content: "";
181
+ position: absolute;
182
+ left: 0; top: 4px; bottom: 4px;
183
+ width: 2px;
184
+ background: var(--accent);
185
+ border-radius: 1px;
186
+ }
187
+ .sidebar-bar {
188
+ position: absolute;
189
+ left: 0; top: 0; bottom: 0;
190
+ background: var(--bg-2);
191
+ z-index: 0;
192
+ pointer-events: none;
193
+ opacity: 0.6;
194
+ }
195
+ .sidebar-row.is-active .sidebar-bar { background: var(--selected-bar); opacity: 0.7; }
196
+ .sidebar-row-label, .sidebar-count { position: relative; z-index: 1; }
197
+ .sidebar-row-label {
198
+ display: flex;
199
+ align-items: center;
200
+ gap: 6px;
201
+ min-width: 0;
202
+ overflow: hidden;
203
+ white-space: nowrap;
204
+ }
205
+ .sidebar-model { overflow: hidden; text-overflow: ellipsis; }
206
+ .sidebar-count {
207
+ font-family: "JetBrains Mono", monospace;
208
+ font-size: 11px;
209
+ color: var(--fg-3);
210
+ font-variant-numeric: tabular-nums;
211
+ }
212
+ .sidebar-row.is-active .sidebar-count { color: var(--fg-1); }
213
+ .sidebar-all .sidebar-row-label { font-weight: 500; }
214
+ .sidebar-all .dot {
215
+ width: 6px; height: 6px; border-radius: 50%;
216
+ background: var(--accent);
217
+ box-shadow: 0 0 0 2px color-mix(in oklab, var(--accent) 25%, transparent);
218
+ }
219
+
220
+ .sidebar-tag {
221
+ display: inline-flex;
222
+ align-items: center;
223
+ height: 14px;
224
+ padding: 0 4px;
225
+ border: 1px solid var(--border-strong);
226
+ border-radius: 2px;
227
+ font-size: 9px;
228
+ color: var(--fg-3);
229
+ background: var(--bg-2);
230
+ font-family: "JetBrains Mono", monospace;
231
+ }
232
+ .sidebar-tag.mask { color: var(--mask); border-color: color-mix(in oklab, var(--mask) 30%, var(--border)); }
233
+
234
+ .mode-dot {
235
+ width: 6px; height: 6px;
236
+ border-radius: 50%;
237
+ background: var(--mode-identity);
238
+ flex-shrink: 0;
239
+ }
240
+ .mode-dot.mode-identity { background: var(--mode-identity); }
241
+ .mode-dot.mode-only { background: var(--mode-only); }
242
+ .mode-dot.mode-snapshot { background: var(--mode-snapshot); }
243
+
244
+ .sidebar-foot {
245
+ border-top: 1px solid var(--border);
246
+ padding: 8px 12px;
247
+ display: flex;
248
+ flex-direction: column;
249
+ gap: 6px;
250
+ background: var(--bg-1);
251
+ }
252
+ .legend {
253
+ display: flex;
254
+ gap: 10px;
255
+ font-size: 10px;
256
+ color: var(--fg-3);
257
+ font-family: "JetBrains Mono", monospace;
258
+ }
259
+ .legend span { display: inline-flex; align-items: center; gap: 4px; }
260
+ .legend i { width: 6px; height: 6px; border-radius: 50%; display: inline-block; }
261
+ .retention {
262
+ display: flex;
263
+ justify-content: space-between;
264
+ align-items: baseline;
265
+ font-size: 10.5px;
266
+ }
267
+ .retention-label { text-transform: uppercase; letter-spacing: 0.06em; color: var(--fg-3); font-size: 9.5px; }
268
+ .retention-val { font-family: "JetBrains Mono", monospace; color: var(--fg-2); }
269
+
270
+ /* ===== main ===== */
271
+ .main {
272
+ display: flex;
273
+ flex-direction: column;
274
+ min-width: 0;
275
+ overflow: hidden;
276
+ }
277
+
278
+ /* topbar */
279
+ .topbar {
280
+ height: 38px;
281
+ flex-shrink: 0;
282
+ display: flex;
283
+ align-items: center;
284
+ justify-content: space-between;
285
+ padding: 0 14px;
286
+ border-bottom: 1px solid var(--border);
287
+ background: var(--bg-1);
288
+ }
289
+ .crumbs {
290
+ display: flex;
291
+ align-items: center;
292
+ gap: 8px;
293
+ min-width: 0;
294
+ overflow: hidden;
295
+ }
296
+ .crumb { color: var(--fg-2); font-size: 12px; }
297
+ .crumb-active { color: var(--fg); font-weight: 500; }
298
+ .crumb-sep { color: var(--fg-4); }
299
+ .crumb-meta { color: var(--fg-3); font-size: 11px; }
300
+ .topbar-right { display: flex; gap: 8px; align-items: center; }
301
+ .conn-pill, .time-pill {
302
+ display: inline-flex; align-items: center; gap: 6px;
303
+ height: 22px;
304
+ padding: 0 8px;
305
+ border: 1px solid var(--border);
306
+ border-radius: 3px;
307
+ background: var(--bg-2);
308
+ font-size: 11px;
309
+ }
310
+ .conn-dot {
311
+ width: 6px; height: 6px;
312
+ background: var(--conn);
313
+ border-radius: 50%;
314
+ box-shadow: 0 0 0 2px color-mix(in oklab, var(--conn) 22%, transparent);
315
+ }
316
+ .time-pill { color: var(--fg-2); }
317
+
318
+ /* kpi strip */
319
+ .kpi-strip {
320
+ display: grid;
321
+ grid-template-columns: repeat(5, 1fr) 1.4fr;
322
+ gap: 0;
323
+ border-bottom: 1px solid var(--border);
324
+ background: var(--bg-1);
325
+ }
326
+ .kpi {
327
+ padding: 10px 14px;
328
+ border-right: 1px solid var(--border);
329
+ display: flex;
330
+ flex-direction: column;
331
+ gap: 2px;
332
+ }
333
+ .kpi:last-child { border-right: 0; }
334
+ .kpi-label {
335
+ font-size: 9.5px;
336
+ text-transform: uppercase;
337
+ letter-spacing: 0.07em;
338
+ color: var(--fg-3);
339
+ }
340
+ .kpi-value {
341
+ font-family: "JetBrains Mono", monospace;
342
+ font-size: 18px;
343
+ font-weight: 500;
344
+ color: var(--fg);
345
+ font-variant-numeric: tabular-nums;
346
+ letter-spacing: -0.01em;
347
+ }
348
+ .kpi-sub {
349
+ font-size: 11px;
350
+ color: var(--fg-3);
351
+ font-family: "JetBrains Mono", monospace;
352
+ }
353
+ .spark-kpi { padding-bottom: 8px; }
354
+ .spark {
355
+ display: flex;
356
+ align-items: flex-end;
357
+ gap: 2px;
358
+ height: 28px;
359
+ margin-top: 4px;
360
+ }
361
+ .spark-bar {
362
+ flex: 1;
363
+ min-height: 1px;
364
+ background: var(--fg-3);
365
+ border-radius: 1px;
366
+ opacity: 0.7;
367
+ }
368
+ .spark-bar:nth-last-child(-n+3) { background: var(--accent); opacity: 1; }
369
+
370
+ /* filter bar */
371
+ .filter-bar {
372
+ display: flex;
373
+ align-items: center;
374
+ gap: 10px;
375
+ padding: 8px 14px;
376
+ border-bottom: 1px solid var(--border);
377
+ background: var(--bg-1);
378
+ flex-wrap: wrap;
379
+ }
380
+ .filter-search {
381
+ flex: 1;
382
+ min-width: 220px;
383
+ display: flex;
384
+ align-items: center;
385
+ gap: 6px;
386
+ background: var(--bg-2);
387
+ border: 1px solid var(--border);
388
+ border-radius: 4px;
389
+ padding: 4px 8px;
390
+ height: 26px;
391
+ color: var(--fg-3);
392
+ }
393
+ .filter-search input {
394
+ flex: 1; min-width: 0;
395
+ background: transparent; border: 0; outline: 0;
396
+ color: var(--fg);
397
+ font-size: 11.5px;
398
+ }
399
+ .seg, .filter-select {
400
+ display: inline-flex;
401
+ align-items: center;
402
+ height: 26px;
403
+ border: 1px solid var(--border);
404
+ border-radius: 4px;
405
+ background: var(--bg-2);
406
+ overflow: hidden;
407
+ }
408
+ .seg-label {
409
+ padding: 0 8px;
410
+ font-size: 10px;
411
+ text-transform: uppercase;
412
+ letter-spacing: 0.06em;
413
+ color: var(--fg-3);
414
+ border-right: 1px solid var(--border);
415
+ height: 100%;
416
+ display: inline-flex;
417
+ align-items: center;
418
+ background: var(--bg-3);
419
+ }
420
+ .seg-btn {
421
+ height: 100%;
422
+ padding: 0 9px;
423
+ background: transparent;
424
+ border: 0;
425
+ color: var(--fg-2);
426
+ font-size: 11.5px;
427
+ border-right: 1px solid var(--border);
428
+ }
429
+ .seg-btn:last-child { border-right: 0; }
430
+ .seg-btn:hover { background: var(--bg-row-hover); color: var(--fg); }
431
+ .seg-btn.is-active { background: var(--selected); color: var(--fg); }
432
+ .filter-select select {
433
+ border: 0; background: transparent;
434
+ color: var(--fg);
435
+ height: 100%;
436
+ padding: 0 8px;
437
+ font-size: 11.5px;
438
+ outline: 0;
439
+ cursor: pointer;
440
+ }
441
+ .filter-clear {
442
+ height: 26px;
443
+ padding: 0 10px;
444
+ border: 1px solid var(--border);
445
+ background: transparent;
446
+ color: var(--fg-2);
447
+ border-radius: 4px;
448
+ font-size: 11.5px;
449
+ }
450
+ .filter-clear:hover { color: var(--fg); border-color: var(--border-strong); }
451
+
452
+ /* table */
453
+ .table-wrap {
454
+ flex: 1;
455
+ overflow: auto;
456
+ background: var(--bg);
457
+ }
458
+ .table-wrap::-webkit-scrollbar { width: 10px; height: 10px; }
459
+ .table-wrap::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 5px; }
460
+ .dtable {
461
+ width: 100%;
462
+ border-collapse: collapse;
463
+ font-size: 12px;
464
+ }
465
+ .dtable thead th {
466
+ position: sticky; top: 0; z-index: 2;
467
+ background: var(--bg-1);
468
+ text-align: left;
469
+ padding: 7px 10px;
470
+ font-size: 10px;
471
+ text-transform: uppercase;
472
+ letter-spacing: 0.07em;
473
+ color: var(--fg-3);
474
+ font-weight: 500;
475
+ border-bottom: 1px solid var(--border);
476
+ }
477
+ .th-expand { width: 24px; padding-right: 0 !important; }
478
+ .th-when { width: 138px; }
479
+ .th-id { width: 84px; text-align: right; }
480
+ .th-summary { width: auto; }
481
+
482
+ .drow { cursor: pointer; }
483
+ .drow > td {
484
+ padding: 6px 10px;
485
+ border-bottom: 1px solid var(--border);
486
+ vertical-align: middle;
487
+ white-space: nowrap;
488
+ overflow: hidden;
489
+ text-overflow: ellipsis;
490
+ }
491
+ .drow:hover > td { background: var(--bg-row-hover); }
492
+ .drow.is-open > td { background: var(--bg-row-open); border-bottom-color: transparent; }
493
+ .drow.is-trunc .kind-icon { color: var(--truncate); }
494
+ .drow.is-trunc .record-type { color: var(--truncate); }
495
+
496
+ .td-expand { color: var(--fg-3); padding-right: 0 !important; }
497
+ .drow.is-open .td-expand { color: var(--fg); }
498
+
499
+ .td-when { font-family: "JetBrains Mono", monospace; }
500
+ .when-rel { color: var(--fg); font-size: 11.5px; font-variant-numeric: tabular-nums; }
501
+ .when-abs { color: var(--fg-3); font-size: 10.5px; font-variant-numeric: tabular-nums; }
502
+
503
+ .kind-icon { display: inline-block; margin-right: 6px; color: var(--kind-delete); vertical-align: -1px; }
504
+ .record-type { font-weight: 500; color: var(--fg); }
505
+ .record-id-cell { display: inline-flex; align-items: center; gap: 2px; margin-left: 6px; }
506
+ .record-id { font-family: "JetBrains Mono", monospace; color: var(--fg-2); font-size: 11.5px; }
507
+
508
+ .actor { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
509
+ .actor-name { color: var(--fg-1); }
510
+ .actor-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--fg-3); flex-shrink: 0; }
511
+ .actor-admin .actor-dot, .actor-engineer .actor-dot { background: var(--mode-only); }
512
+ .actor-job .actor-dot { background: var(--mode-snapshot); }
513
+ .actor-db .actor-dot { background: var(--truncate); }
514
+ .actor-anonymous .actor-dot { background: var(--fg-4); }
515
+ .actor-job .actor-name, .actor-db .actor-name { font-family: "JetBrains Mono", monospace; font-size: 11.5px; color: var(--fg-2); }
516
+
517
+ .td-summary { color: var(--fg-2); max-width: 0; }
518
+ .summary-bits { display: inline-flex; gap: 6px; align-items: center; }
519
+ .dot-sep { color: var(--fg-4); }
520
+ .reason { font-style: italic; color: var(--fg-2); }
521
+ .meta-kv { display: inline-flex; gap: 4px; align-items: baseline; font-family: "JetBrains Mono", monospace; font-size: 11px; }
522
+ .meta-k { color: var(--fg-3); }
523
+ .meta-v { color: var(--fg-1); max-width: 240px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
524
+
525
+ .td-id { text-align: right; color: var(--fg-3); font-size: 11px; font-variant-numeric: tabular-nums; }
526
+
527
+ .dim { color: var(--fg-3); }
528
+ .muted { color: var(--fg-4); }
529
+
530
+ /* pills */
531
+ .pill {
532
+ display: inline-flex; align-items: center;
533
+ height: 16px;
534
+ padding: 0 5px;
535
+ border-radius: 2px;
536
+ font-size: 10px;
537
+ border: 1px solid var(--border-strong);
538
+ background: var(--bg-2);
539
+ color: var(--fg-2);
540
+ font-family: "JetBrains Mono", monospace;
541
+ letter-spacing: 0.02em;
542
+ margin-right: 4px;
543
+ }
544
+ .pill:last-child { margin-right: 0; }
545
+ .pill.subtle { color: var(--fg-3); }
546
+ .mode-pill.mode-identity { color: var(--mode-identity); border-color: color-mix(in oklab, var(--mode-identity) 35%, var(--border)); }
547
+ .mode-pill.mode-only { color: var(--mode-only); border-color: color-mix(in oklab, var(--mode-only) 35%, var(--border)); background: color-mix(in oklab, var(--mode-only) 8%, var(--bg-2)); }
548
+ .mode-pill.mode-snapshot { color: var(--mode-snapshot); border-color: color-mix(in oklab, var(--mode-snapshot) 35%, var(--border)); background: color-mix(in oklab, var(--mode-snapshot) 8%, var(--bg-2)); }
549
+ .mask-pill { color: var(--mask); border-color: color-mix(in oklab, var(--mask) 35%, var(--border)); }
550
+
551
+ /* copy button */
552
+ .copy-btn {
553
+ display: inline-flex; align-items: center; justify-content: center;
554
+ width: 16px; height: 16px;
555
+ border: 0; background: transparent;
556
+ color: var(--fg-3);
557
+ border-radius: 2px;
558
+ margin-left: 2px;
559
+ vertical-align: -2px;
560
+ }
561
+ .copy-btn:hover { background: var(--bg-3); color: var(--fg); }
562
+
563
+ /* detail */
564
+ .drow-detail > td {
565
+ padding: 0;
566
+ background: var(--bg-detail);
567
+ border-bottom: 1px solid var(--border);
568
+ }
569
+ .detail {
570
+ padding: 12px 18px 14px;
571
+ border-top: 1px dashed var(--border-strong);
572
+ }
573
+ .detail-grid {
574
+ display: grid;
575
+ grid-template-columns: 1fr 1fr;
576
+ gap: 14px 24px;
577
+ }
578
+ .detail-section { min-width: 0; }
579
+ .detail-wide { grid-column: span 2; }
580
+ .detail-h {
581
+ display: flex;
582
+ align-items: center;
583
+ gap: 6px;
584
+ font-size: 10px;
585
+ text-transform: uppercase;
586
+ letter-spacing: 0.08em;
587
+ color: var(--fg-3);
588
+ padding-bottom: 6px;
589
+ margin-bottom: 6px;
590
+ border-bottom: 1px solid var(--border);
591
+ }
592
+ .detail-h .pill { margin-left: auto; margin-right: 0; }
593
+ .detail-h .pill + .pill { margin-left: 6px; }
594
+
595
+ .kv-table { width: 100%; font-size: 11.5px; }
596
+ .kv-table td { padding: 2px 0; vertical-align: top; }
597
+ .kv-key {
598
+ font-family: "JetBrains Mono", monospace;
599
+ color: var(--fg-3);
600
+ width: 140px;
601
+ white-space: nowrap;
602
+ }
603
+ .kv-val {
604
+ font-family: "JetBrains Mono", monospace;
605
+ color: var(--fg);
606
+ word-break: break-all;
607
+ }
608
+ .kv-val .masked { color: var(--mask); }
609
+ .kv-val .num { color: var(--mode-only); }
610
+ .kv-val .bool { color: var(--mode-snapshot); }
611
+
612
+ .json-empty {
613
+ font-family: "JetBrains Mono", monospace;
614
+ color: var(--fg-3);
615
+ font-size: 11.5px;
616
+ padding: 4px 0;
617
+ }
618
+
619
+ .code {
620
+ background: var(--bg-2);
621
+ border: 1px solid var(--border);
622
+ border-radius: 3px;
623
+ padding: 8px 10px;
624
+ font-size: 11.5px;
625
+ color: var(--fg-1);
626
+ margin: 4px 0 0;
627
+ white-space: pre;
628
+ overflow-x: auto;
629
+ }
630
+
631
+ /* pager */
632
+ .pager {
633
+ flex-shrink: 0;
634
+ display: flex;
635
+ justify-content: space-between;
636
+ align-items: center;
637
+ padding: 8px 14px;
638
+ border-top: 1px solid var(--border);
639
+ background: var(--bg-1);
640
+ height: 36px;
641
+ font-size: 11.5px;
642
+ }
643
+ .pager-info { color: var(--fg-2); }
644
+ .pager-info .mono { color: var(--fg); font-variant-numeric: tabular-nums; }
645
+ .pager-btns { display: flex; align-items: center; gap: 4px; }
646
+ .pager-btns button {
647
+ width: 24px; height: 22px;
648
+ border: 1px solid var(--border);
649
+ background: var(--bg-2);
650
+ color: var(--fg-1);
651
+ border-radius: 3px;
652
+ font-size: 11px;
653
+ }
654
+ .pager-btns button:hover:not(:disabled) { background: var(--bg-3); }
655
+ .pager-btns button:disabled { color: var(--fg-4); cursor: not-allowed; }
656
+ .pager-page { color: var(--fg-2); padding: 0 8px; font-variant-numeric: tabular-nums; }
657
+
658
+ /* empty */
659
+ .table-empty {
660
+ display: flex;
661
+ flex-direction: column;
662
+ align-items: center;
663
+ justify-content: center;
664
+ padding: 80px 20px;
665
+ color: var(--fg-3);
666
+ gap: 6px;
667
+ }
668
+ .empty-mark { font-size: 28px; color: var(--fg-4); font-family: "JetBrains Mono", monospace; }
669
+ .empty-title { color: var(--fg-1); font-weight: 500; }
670
+ .empty-sub { font-size: 11px; color: var(--fg-3); font-family: "JetBrains Mono", monospace; }
671
+
672
+ /* Theme toggle — these rules live in an inline <style> block in the
673
+ prototype's index.html, so the verbatim styles.css port missed them. */
674
+ .theme-btn {
675
+ width: 22px; height: 22px;
676
+ border: 1px solid var(--border);
677
+ background: var(--bg-2);
678
+ color: var(--fg-1);
679
+ border-radius: 3px;
680
+ font-size: 13px;
681
+ line-height: 1;
682
+ display: inline-flex; align-items: center; justify-content: center;
683
+ cursor: pointer;
684
+ }
685
+ .theme-btn:hover { background: var(--bg-3); }
686
+
687
+ /* Copy button — match the trash/kind icon visually: 11×11 SVG with a fuller
688
+ body (defined in dashboard_helper#icon_copy) instead of the prototype's
689
+ sparse path. Wrapper is tight to the icon, no extra box. */
690
+ .copy-btn {
691
+ display: inline-flex;
692
+ align-items: center;
693
+ padding: 0;
694
+ margin-left: 4px;
695
+ vertical-align: -1px;
696
+ color: var(--fg-2);
697
+ }
698
+ .copy-btn:hover { color: var(--fg); }
699
+
700
+ /* ===== engine adapters =====
701
+ The prototype's segmented controls and sidebar rows are <button> elements
702
+ driven by JS state. Our Hotwire build uses <a> for Turbo navigation, so
703
+ these rules teach links to behave like the buttons they replaced. */
704
+
705
+ /* Strip the browser's default <a> underline for any link the dashboard styles. */
706
+ .app a { text-decoration: none; color: inherit; }
707
+
708
+ /* Segment buttons (time / mode / kind) need explicit flex/height/box-sizing
709
+ when rendered as <a> rather than <button>. */
710
+ .seg-btn {
711
+ display: inline-flex;
712
+ align-items: center;
713
+ box-sizing: border-box;
714
+ }
715
+
716
+ /* Pager nav buttons rendered as <a> need to match the prototype's <button>
717
+ box styling (the prototype CSS targets `.pager-btns button` exclusively). */
718
+ .pager-btns a {
719
+ width: 24px;
720
+ height: 22px;
721
+ border: 1px solid var(--border);
722
+ background: var(--bg-2);
723
+ color: var(--fg-1);
724
+ border-radius: 3px;
725
+ font-size: 11px;
726
+ display: inline-flex;
727
+ align-items: center;
728
+ justify-content: center;
729
+ }
730
+ .pager-btns a:hover { background: var(--bg-3); }
731
+
732
+ /* `clear` is an <a> in our build; match the prototype's <button> sizing. */
733
+ a.filter-clear {
734
+ display: inline-flex;
735
+ align-items: center;
736
+ }
737
+
738
+ /* Form holds search + actor side-by-side. `flex-shrink: 0` keeps the form at
739
+ its content width so the actor select never overlaps the segments; when
740
+ the row can't fit everything, `.filter-bar`'s `flex-wrap: wrap` pushes
741
+ segs to a second row instead. */
742
+ .filter-form {
743
+ display: flex;
744
+ align-items: center;
745
+ gap: 10px;
746
+ flex: 1 0 auto;
747
+ }
748
+ .filter-form .filter-search { flex: 1 1 220px; min-width: 0; }
749
+ .filter-form .filter-select { flex: 0 0 auto; }
750
+
751
+ /* Visually-hidden text for screen readers (labels, status announcements). */
752
+ .sr-only {
753
+ position: absolute;
754
+ width: 1px;
755
+ height: 1px;
756
+ padding: 0;
757
+ margin: -1px;
758
+ overflow: hidden;
759
+ clip: rect(0, 0, 0, 0);
760
+ white-space: nowrap;
761
+ border: 0;
762
+ }
763
+
764
+ /* The dashboard wrapper and its inner regions sit between .main (flex column,
765
+ owns scroll boundary) and .table-wrap (needs flex: 1 + overflow: auto to
766
+ scroll the table independently). `display: contents` makes them transparent
767
+ to layout so their children become direct flex children of .main, restoring
768
+ the prototype's vertical-fill / table-scroll behavior. */
769
+ #athar-dashboard,
770
+ #athar-pre,
771
+ #athar-post { display: contents; }
772
+
773
+ /* Loading indicator: while a partial fetch is in flight (the partial-link
774
+ delegated handler toggles `is-loading` on the swap regions), dim the
775
+ affected sections and disable pointer events to prevent double-submission.
776
+ The 200ms delay before fade-in avoids flicker on fast loads; the un-loading
777
+ state has a short symmetric fade-in so the snap-back isn't abrupt.
778
+
779
+ The filter bar is intentionally never dimmed — it lives outside both swap
780
+ regions and its descendants are not selected here. That keeps every search
781
+ keystroke flash-free. */
782
+ #athar-pre .kpi-strip,
783
+ #athar-post .table-wrap,
784
+ #athar-post .pager {
785
+ transition: opacity 120ms ease-in;
786
+ }
787
+ #athar-pre.is-loading .kpi-strip,
788
+ #athar-post.is-loading .table-wrap,
789
+ #athar-post.is-loading .pager {
790
+ opacity: 0.6;
791
+ transition: opacity 120ms 200ms ease-out;
792
+ pointer-events: none;
793
+ }
794
+
795
+ /* "All deletions" sits directly under the brand row; without a gap it
796
+ visually collides with the head section. Specificity raised because
797
+ .sidebar-row's later `margin: 0 6px` would otherwise win the tie. */
798
+ .sidebar-row.sidebar-all { margin-top: 14px; }
799
+
800
+ /* Native <select> arrows are browser-positioned and can't be styled, so
801
+ suppress the native arrow and render our own chevron as an absolutely
802
+ positioned SVG (rendered inline next to the <select>). */
803
+ /* The actor select shows long emails as options. Without explicit sizing,
804
+ appearance:none lets the select grow to its widest option (~250px),
805
+ crowding out the time/mode/kind segments. Cap to a sensible width and
806
+ render our own chevron next to it. The native dropdown popup ignores
807
+ width, so long labels remain fully visible when opened. */
808
+ .filter-select { position: relative; }
809
+ .filter-select select {
810
+ appearance: none;
811
+ -webkit-appearance: none;
812
+ -moz-appearance: none;
813
+ width: 220px;
814
+ padding-right: 24px;
815
+ text-overflow: ellipsis;
816
+ }
817
+ .filter-select > svg {
818
+ position: absolute;
819
+ right: 8px;
820
+ top: 50%;
821
+ transform: translateY(-50%);
822
+ color: var(--fg-2);
823
+ pointer-events: none;
824
+ }
825
+
826
+ /* Copy button on requery code blocks: positioned top-right, with a small
827
+ backdrop so the icon stays legible over the code text. */
828
+ .code-wrap { position: relative; }
829
+ .code-wrap .code { padding-right: 36px; }
830
+ .code-wrap > .copy-btn {
831
+ position: absolute;
832
+ top: 6px;
833
+ right: 6px;
834
+ width: 22px;
835
+ height: 22px;
836
+ background: var(--bg-3);
837
+ border: 1px solid var(--border);
838
+ border-radius: 3px;
839
+ margin: 0;
840
+ }
841
+ .code-wrap > .copy-btn:hover { background: var(--bg-2); color: var(--fg); }