rails_event_viewer 0.3.1 → 0.4.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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/README.md +5 -0
  4. data/app/assets/stylesheets/rails_event_viewer/application.css +1 -1
  5. data/app/controllers/rails_event_viewer/application_controller.rb +1 -11
  6. data/app/controllers/rails_event_viewer/events_controller.rb +6 -6
  7. data/app/helpers/rails_event_viewer/application_helper.rb +21 -75
  8. data/app/models/rails_event_viewer/entry.rb +0 -26
  9. data/app/models/rails_event_viewer/page.rb +9 -0
  10. data/app/views/layouts/rails_event_viewer/application.html.erb +3 -4
  11. data/app/views/rails_event_viewer/analytics/overview.html.erb +11 -9
  12. data/app/views/rails_event_viewer/dashboard/index.html.erb +13 -13
  13. data/app/views/rails_event_viewer/event_types/index.html.erb +6 -6
  14. data/app/views/rails_event_viewer/event_types/show.html.erb +14 -14
  15. data/app/views/rails_event_viewer/events/_event_row.html.erb +8 -8
  16. data/app/views/rails_event_viewer/events/_filters_sidebar.html.erb +16 -14
  17. data/app/views/rails_event_viewer/events/index.html.erb +6 -6
  18. data/app/views/rails_event_viewer/events/show.html.erb +22 -22
  19. data/app/views/rails_event_viewer/groups/index.html.erb +7 -7
  20. data/app/views/rails_event_viewer/groups/show.html.erb +11 -11
  21. data/app/views/rails_event_viewer/shared/_navigation.html.erb +8 -14
  22. data/app/views/rails_event_viewer/shared/_pagination.html.erb +10 -10
  23. data/app/views/rails_event_viewer/shared/_stat_card.html.erb +5 -5
  24. data/db/migrate/20261002121337_create_rails_event_viewer_entries.rb +0 -1
  25. data/lib/generators/rails_event_viewer/install/templates/create_rails_event_viewer_entries.rb.erb +0 -1
  26. data/lib/rails_event_viewer/adapter.rb +0 -8
  27. data/lib/rails_event_viewer/adapters/active_record.rb +8 -44
  28. data/lib/rails_event_viewer/adapters/concerns/in_memory_filtering.rb +0 -7
  29. data/lib/rails_event_viewer/adapters/memory.rb +0 -4
  30. data/lib/rails_event_viewer/adapters/null.rb +0 -8
  31. data/lib/rails_event_viewer/adapters/redis.rb +3 -17
  32. data/lib/rails_event_viewer/flusher.rb +5 -5
  33. data/lib/rails_event_viewer/logging.rb +1 -11
  34. data/lib/rails_event_viewer/version.rb +1 -1
  35. data/lib/rails_event_viewer.rb +5 -5
  36. metadata +7 -2
  37. data/app/assets/stylesheets/rails_event_viewer/tailwind.css +0 -29
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 876a5c4676cfb1f5512d82e73464c1e69a19bd9b1a1b5c2854f93aef7c412a60
4
- data.tar.gz: 766e263c994dc9552472e065e1abaea18064cc08858c45ef524148923693922f
3
+ metadata.gz: 935a008c1e9519c485ee47d472d8980a36e1e6e4778e83c76e38629350ed93e9
4
+ data.tar.gz: 9244a13f193475b62847af18366b4e53246a9602f84c13cd00114ec6ead86c72
5
5
  SHA512:
6
- metadata.gz: b3b9d72c97349b344106c874be9988e605c05efda5cafdb24e8230603830d313cb3a1b2fb72722a59ce6a27dab43e08751abebcd39606fd23ff2337c84392eac
7
- data.tar.gz: 4e0cbc54525cdaa6b177832bd53e7207d1f3fc4c0a6167c3da250e85b00bea27bdbf1073e7a555b65887514bcc6e4e1ccdfe18b53c8a6fd1ffd49b74c82511a0
6
+ metadata.gz: af5581280410b7b5eb3a79ce61b3dc53ca8dac9ddb083d79554c61f950977319049714334c6fb19713d6aa8c67b38bb63a2bb1dd9042a08a71da7def2bd6b5e1
7
+ data.tar.gz: bc898a58889c8786fcc83c6467084275637c5e27ddc8ee33a233c571d917724b65445f235eebcf3e99be1e2aea70c4eb6a301d764aa5a3bb2ac53ce40be16750
data/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0 (2026-10-03)
4
+
5
+ Breaking:
6
+
7
+ - Removed `Adapter#distinct_group_values` and `Adapter#supports_retention?`. Nothing called them.
8
+ - Removed `Entry#source_location`, `#short_filepath`, `#tagged?`, `#has_context?` and `#tag`.
9
+ - The ActiveRecord adapter raises on unknown `adapter_options`, like the other adapters.
10
+ - Times show in the host app's time zone. The engine no longer reads a `timezone` cookie.
11
+
12
+ Changed:
13
+
14
+ - New installs skip the index on `name` alone. The `(name, occurred_at)` index covers it. Existing apps can drop it with `remove_index :rails_event_viewer_entries, :name`.
15
+ - Chartkick is pinned below 6, to match the bundled Chartkick.js.
16
+ - Dashboard scripts load with `defer`.
17
+
18
+ Fixed:
19
+
20
+ - Events with the same timestamp keep a stable order across pages.
21
+ - Pagination renders only the page links it shows, not one loop per page.
22
+ - Related events look one hour either side of the event, so the lookup no longer scans the whole table.
23
+ - Form fields have labels, tables have column headers, and the current page is marked for screen readers. Grey text has more contrast.
24
+
3
25
  ## 0.3.1 (2026-10-03)
4
26
 
5
27
  Changed:
data/README.md CHANGED
@@ -8,6 +8,9 @@ A Rails engine that captures events emitted with `Rails.event` and gives you a d
8
8
 
9
9
  - Rails 8.1+
10
10
  - Ruby 3.3+
11
+ - SQLite, PostgreSQL, or MySQL 8 for the default ActiveRecord adapter
12
+
13
+ API-only apps need an asset pipeline to serve the dashboard's CSS and JavaScript. Add `gem "propshaft"`, and run `assets:precompile` when you deploy.
11
14
 
12
15
  ## Installation
13
16
 
@@ -130,6 +133,8 @@ production:
130
133
 
131
134
  ## Development
132
135
 
136
+ The dashboard ships Chart.js 4.4.1, chartjs-adapter-date-fns 3.0.0 and Chartkick.js 5.0.1.
137
+
133
138
  ```bash
134
139
  bundle install
135
140
  bundle exec rake test
@@ -1 +1 @@
1
- *,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.pointer-events-none{pointer-events:none}.absolute{position:absolute}.relative{position:relative}.inset-y-0{top:0;bottom:0}.bottom-0{bottom:0}.left-0{left:0}.left-10{left:2.5rem}.left-12{left:3rem}.top-0{top:0}.top-4{top:1rem}.top-6{top:1.5rem}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.ml-1{margin-left:.25rem}.ml-10{margin-left:2.5rem}.ml-3{margin-left:.75rem}.ml-4{margin-left:1rem}.ml-5{margin-left:1.25rem}.mr-1{margin-right:.25rem}.mr-2{margin-right:.5rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.h-12{height:3rem}.h-2{height:.5rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-72{height:18rem}.h-full{height:100%}.min-h-16{min-height:4rem}.min-h-full{min-height:100%}.w-0{width:0}.w-0\.5{width:.125rem}.w-1\/5{width:20%}.w-12{width:3rem}.w-2\/5{width:40%}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-64{width:16rem}.w-9{width:2.25rem}.w-fit{width:-moz-fit-content;width:fit-content}.w-full{width:100%}.min-w-0{min-width:0}.min-w-full{min-width:100%}.max-w-7xl{max-width:80rem}.max-w-full{max-width:100%}.max-w-md{max-width:28rem}.max-w-xs{max-width:20rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.table-fixed{table-layout:fixed}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-5{gap:1.25rem}.gap-6{gap:1.5rem}.gap-y-2{row-gap:.5rem}.space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.25rem*var(--tw-space-x-reverse));margin-left:calc(.25rem*(1 - var(--tw-space-x-reverse)))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.5rem*var(--tw-space-x-reverse));margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)))}.space-x-3>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.75rem*var(--tw-space-x-reverse));margin-left:calc(.75rem*(1 - var(--tw-space-x-reverse)))}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem*var(--tw-space-x-reverse));margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.25rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.25rem*var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem*var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px*var(--tw-divide-y-reverse))}.divide-gray-100>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(243 244 246/var(--tw-divide-opacity,1))}.divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(229 231 235/var(--tw-divide-opacity,1))}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.truncate{overflow:hidden;text-overflow:ellipsis}.truncate,.whitespace-nowrap{white-space:nowrap}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-t{border-top-width:1px}.border-gray-200{--tw-border-opacity:1;border-color:rgb(229 231 235/var(--tw-border-opacity,1))}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity,1))}.border-indigo-500{--tw-border-opacity:1;border-color:rgb(99 102 241/var(--tw-border-opacity,1))}.border-indigo-600{--tw-border-opacity:1;border-color:rgb(79 70 229/var(--tw-border-opacity,1))}.border-yellow-200{--tw-border-opacity:1;border-color:rgb(254 240 138/var(--tw-border-opacity,1))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity,1))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity,1))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity,1))}.bg-green-50{--tw-bg-opacity:1;background-color:rgb(240 253 244/var(--tw-bg-opacity,1))}.bg-indigo-100{--tw-bg-opacity:1;background-color:rgb(224 231 255/var(--tw-bg-opacity,1))}.bg-indigo-500{--tw-bg-opacity:1;background-color:rgb(99 102 241/var(--tw-bg-opacity,1))}.bg-indigo-600{--tw-bg-opacity:1;background-color:rgb(79 70 229/var(--tw-bg-opacity,1))}.bg-indigo-700{--tw-bg-opacity:1;background-color:rgb(67 56 202/var(--tw-bg-opacity,1))}.bg-red-50{--tw-bg-opacity:1;background-color:rgb(254 242 242/var(--tw-bg-opacity,1))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.bg-yellow-100{--tw-bg-opacity:1;background-color:rgb(254 249 195/var(--tw-bg-opacity,1))}.bg-yellow-50{--tw-bg-opacity:1;background-color:rgb(254 252 232/var(--tw-bg-opacity,1))}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.pl-10{padding-left:2.5rem}.pl-16{padding-left:4rem}.pl-3{padding-left:.75rem}.pl-4{padding-left:1rem}.pr-3{padding-right:.75rem}.pr-6{padding-right:1.5rem}.pt-3{padding-top:.75rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.tracking-wider{letter-spacing:.05em}.text-blue-600{--tw-text-opacity:1;color:rgb(37 99 235/var(--tw-text-opacity,1))}.text-gray-300{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity,1))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity,1))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity,1))}.text-gray-600{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity,1))}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity,1))}.text-gray-800{--tw-text-opacity:1;color:rgb(31 41 55/var(--tw-text-opacity,1))}.text-gray-900{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity,1))}.text-green-600{--tw-text-opacity:1;color:rgb(22 163 74/var(--tw-text-opacity,1))}.text-green-800{--tw-text-opacity:1;color:rgb(22 101 52/var(--tw-text-opacity,1))}.text-indigo-200{--tw-text-opacity:1;color:rgb(199 210 254/var(--tw-text-opacity,1))}.text-indigo-600{--tw-text-opacity:1;color:rgb(79 70 229/var(--tw-text-opacity,1))}.text-indigo-800{--tw-text-opacity:1;color:rgb(55 48 163/var(--tw-text-opacity,1))}.text-purple-600{--tw-text-opacity:1;color:rgb(147 51 234/var(--tw-text-opacity,1))}.text-red-800{--tw-text-opacity:1;color:rgb(153 27 27/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.text-yellow-400{--tw-text-opacity:1;color:rgb(250 204 21/var(--tw-text-opacity,1))}.text-yellow-700{--tw-text-opacity:1;color:rgb(161 98 7/var(--tw-text-opacity,1))}.text-yellow-800{--tw-text-opacity:1;color:rgb(133 77 14/var(--tw-text-opacity,1))}.placeholder-indigo-200::-moz-placeholder{--tw-placeholder-opacity:1;color:rgb(199 210 254/var(--tw-placeholder-opacity,1))}.placeholder-indigo-200::placeholder{--tw-placeholder-opacity:1;color:rgb(199 210 254/var(--tw-placeholder-opacity,1))}.shadow{--tw-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1);--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)}.shadow,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.json-tree{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.875rem}.json-tree-key{color:#4b5563;font-weight:500}.json-tree-string{color:#059669}.json-tree-number{color:#2563eb}.json-tree-boolean{color:#7c3aed}.json-tree-null{color:#9ca3af}.hover\:bg-gray-200:hover{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity,1))}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity,1))}.hover\:bg-indigo-500:hover{--tw-bg-opacity:1;background-color:rgb(99 102 241/var(--tw-bg-opacity,1))}.hover\:bg-indigo-700:hover{--tw-bg-opacity:1;background-color:rgb(67 56 202/var(--tw-bg-opacity,1))}.hover\:text-gray-600:hover{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity,1))}.hover\:text-indigo-500:hover{--tw-text-opacity:1;color:rgb(99 102 241/var(--tw-text-opacity,1))}.hover\:text-indigo-900:hover{--tw-text-opacity:1;color:rgb(49 46 129/var(--tw-text-opacity,1))}.hover\:text-white:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.focus\:border-indigo-500:focus{--tw-border-opacity:1;border-color:rgb(99 102 241/var(--tw-border-opacity,1))}.focus\:bg-white:focus{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.focus\:text-gray-900:focus{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity,1))}.focus\:placeholder-gray-400:focus::-moz-placeholder{--tw-placeholder-opacity:1;color:rgb(156 163 175/var(--tw-placeholder-opacity,1))}.focus\:placeholder-gray-400:focus::placeholder{--tw-placeholder-opacity:1;color:rgb(156 163 175/var(--tw-placeholder-opacity,1))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-indigo-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(99 102 241/var(--tw-ring-opacity,1))}.focus\:ring-white:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(255 255 255/var(--tw-ring-opacity,1))}@media (min-width:640px){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}}@media (min-width:1024px){.lg\:col-span-2{grid-column:span 2/span 2}.lg\:w-72{width:18rem}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:flex-row{flex-direction:row}.lg\:px-8{padding-left:2rem;padding-right:2rem}}
1
+ *,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-none{pointer-events:none}.absolute{position:absolute}.relative{position:relative}.inset-y-0{top:0;bottom:0}.bottom-0{bottom:0}.left-0{left:0}.left-10{left:2.5rem}.left-12{left:3rem}.top-0{top:0}.top-4{top:1rem}.top-6{top:1.5rem}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.ml-1{margin-left:.25rem}.ml-10{margin-left:2.5rem}.ml-3{margin-left:.75rem}.ml-4{margin-left:1rem}.ml-5{margin-left:1.25rem}.mr-1{margin-right:.25rem}.mr-2{margin-right:.5rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.h-12{height:3rem}.h-2{height:.5rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-72{height:18rem}.h-full{height:100%}.min-h-16{min-height:4rem}.min-h-full{min-height:100%}.w-0{width:0}.w-0\.5{width:.125rem}.w-1\/5{width:20%}.w-12{width:3rem}.w-2\/5{width:40%}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-64{width:16rem}.w-9{width:2.25rem}.w-fit{width:-moz-fit-content;width:fit-content}.w-full{width:100%}.min-w-0{min-width:0}.min-w-full{min-width:100%}.max-w-7xl{max-width:80rem}.max-w-full{max-width:100%}.max-w-md{max-width:28rem}.max-w-xs{max-width:20rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.table-fixed{table-layout:fixed}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-5{gap:1.25rem}.gap-6{gap:1.5rem}.gap-y-2{row-gap:.5rem}.space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.25rem*var(--tw-space-x-reverse));margin-left:calc(.25rem*(1 - var(--tw-space-x-reverse)))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.5rem*var(--tw-space-x-reverse));margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)))}.space-x-3>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.75rem*var(--tw-space-x-reverse));margin-left:calc(.75rem*(1 - var(--tw-space-x-reverse)))}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem*var(--tw-space-x-reverse));margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.25rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.25rem*var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem*var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px*var(--tw-divide-y-reverse))}.divide-gray-100>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(243 244 246/var(--tw-divide-opacity,1))}.divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(229 231 235/var(--tw-divide-opacity,1))}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.truncate{overflow:hidden;text-overflow:ellipsis}.truncate,.whitespace-nowrap{white-space:nowrap}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-t{border-top-width:1px}.border-gray-200{--tw-border-opacity:1;border-color:rgb(229 231 235/var(--tw-border-opacity,1))}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity,1))}.border-indigo-500{--tw-border-opacity:1;border-color:rgb(99 102 241/var(--tw-border-opacity,1))}.border-indigo-600{--tw-border-opacity:1;border-color:rgb(79 70 229/var(--tw-border-opacity,1))}.border-yellow-200{--tw-border-opacity:1;border-color:rgb(254 240 138/var(--tw-border-opacity,1))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity,1))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity,1))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity,1))}.bg-green-50{--tw-bg-opacity:1;background-color:rgb(240 253 244/var(--tw-bg-opacity,1))}.bg-indigo-100{--tw-bg-opacity:1;background-color:rgb(224 231 255/var(--tw-bg-opacity,1))}.bg-indigo-500{--tw-bg-opacity:1;background-color:rgb(99 102 241/var(--tw-bg-opacity,1))}.bg-indigo-600{--tw-bg-opacity:1;background-color:rgb(79 70 229/var(--tw-bg-opacity,1))}.bg-indigo-700{--tw-bg-opacity:1;background-color:rgb(67 56 202/var(--tw-bg-opacity,1))}.bg-red-50{--tw-bg-opacity:1;background-color:rgb(254 242 242/var(--tw-bg-opacity,1))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.bg-yellow-100{--tw-bg-opacity:1;background-color:rgb(254 249 195/var(--tw-bg-opacity,1))}.bg-yellow-50{--tw-bg-opacity:1;background-color:rgb(254 252 232/var(--tw-bg-opacity,1))}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.pl-10{padding-left:2.5rem}.pl-16{padding-left:4rem}.pl-3{padding-left:.75rem}.pl-4{padding-left:1rem}.pr-3{padding-right:.75rem}.pr-6{padding-right:1.5rem}.pt-3{padding-top:.75rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.tracking-wider{letter-spacing:.05em}.text-blue-600{--tw-text-opacity:1;color:rgb(37 99 235/var(--tw-text-opacity,1))}.text-gray-300{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity,1))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity,1))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity,1))}.text-gray-600{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity,1))}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity,1))}.text-gray-800{--tw-text-opacity:1;color:rgb(31 41 55/var(--tw-text-opacity,1))}.text-gray-900{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity,1))}.text-green-600{--tw-text-opacity:1;color:rgb(22 163 74/var(--tw-text-opacity,1))}.text-green-800{--tw-text-opacity:1;color:rgb(22 101 52/var(--tw-text-opacity,1))}.text-indigo-200{--tw-text-opacity:1;color:rgb(199 210 254/var(--tw-text-opacity,1))}.text-indigo-600{--tw-text-opacity:1;color:rgb(79 70 229/var(--tw-text-opacity,1))}.text-indigo-800{--tw-text-opacity:1;color:rgb(55 48 163/var(--tw-text-opacity,1))}.text-purple-600{--tw-text-opacity:1;color:rgb(147 51 234/var(--tw-text-opacity,1))}.text-red-800{--tw-text-opacity:1;color:rgb(153 27 27/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.text-yellow-400{--tw-text-opacity:1;color:rgb(250 204 21/var(--tw-text-opacity,1))}.text-yellow-700{--tw-text-opacity:1;color:rgb(161 98 7/var(--tw-text-opacity,1))}.text-yellow-800{--tw-text-opacity:1;color:rgb(133 77 14/var(--tw-text-opacity,1))}.placeholder-indigo-200::-moz-placeholder{--tw-placeholder-opacity:1;color:rgb(199 210 254/var(--tw-placeholder-opacity,1))}.placeholder-indigo-200::placeholder{--tw-placeholder-opacity:1;color:rgb(199 210 254/var(--tw-placeholder-opacity,1))}.shadow{--tw-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1);--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)}.shadow,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.hover\:bg-gray-200:hover{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity,1))}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity,1))}.hover\:bg-indigo-500:hover{--tw-bg-opacity:1;background-color:rgb(99 102 241/var(--tw-bg-opacity,1))}.hover\:bg-indigo-700:hover{--tw-bg-opacity:1;background-color:rgb(67 56 202/var(--tw-bg-opacity,1))}.hover\:text-gray-700:hover{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity,1))}.hover\:text-indigo-500:hover{--tw-text-opacity:1;color:rgb(99 102 241/var(--tw-text-opacity,1))}.hover\:text-indigo-900:hover{--tw-text-opacity:1;color:rgb(49 46 129/var(--tw-text-opacity,1))}.hover\:text-white:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.focus\:border-indigo-500:focus{--tw-border-opacity:1;border-color:rgb(99 102 241/var(--tw-border-opacity,1))}.focus\:bg-white:focus{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.focus\:text-gray-900:focus{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity,1))}.focus\:placeholder-gray-400:focus::-moz-placeholder{--tw-placeholder-opacity:1;color:rgb(156 163 175/var(--tw-placeholder-opacity,1))}.focus\:placeholder-gray-400:focus::placeholder{--tw-placeholder-opacity:1;color:rgb(156 163 175/var(--tw-placeholder-opacity,1))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-indigo-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(99 102 241/var(--tw-ring-opacity,1))}.focus\:ring-white:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(255 255 255/var(--tw-ring-opacity,1))}@media (min-width:640px){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}}@media (min-width:1024px){.lg\:col-span-2{grid-column:span 2/span 2}.lg\:w-72{width:18rem}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:flex-row{flex-direction:row}.lg\:px-8{padding-left:2rem;padding-right:2rem}}
@@ -3,9 +3,8 @@ module RailsEventViewer
3
3
  layout "rails_event_viewer/application"
4
4
 
5
5
  before_action :authenticate!
6
- around_action :use_viewer_time_zone
7
6
 
8
- helper_method :current_timezone, :current_adapter, :analytics_supported?, :persistence_supported?
7
+ helper_method :current_adapter, :analytics_supported?, :persistence_supported?
9
8
 
10
9
  private
11
10
 
@@ -37,11 +36,6 @@ module RailsEventViewer
37
36
  render plain: "Unauthorized", status: :unauthorized unless auth.call(self) || performed?
38
37
  end
39
38
 
40
- def use_viewer_time_zone(&block)
41
- zone = ActiveSupport::TimeZone[cookies[:timezone].to_s] || ActiveSupport::TimeZone["UTC"]
42
- Time.use_zone(zone, &block)
43
- end
44
-
45
39
  def string_param(key)
46
40
  value = params[key]
47
41
  value if value.is_a?(String) && !value.empty?
@@ -56,10 +50,6 @@ module RailsEventViewer
56
50
  default
57
51
  end
58
52
 
59
- def current_timezone
60
- Time.zone.name
61
- end
62
-
63
53
  def current_adapter
64
54
  RailsEventViewer.adapter
65
55
  end
@@ -39,20 +39,20 @@ module RailsEventViewer
39
39
  end
40
40
 
41
41
  def find_related_events(event)
42
- context = event.respond_to?(:context) ? event.context : event[:context]
42
+ context = event[:context]
43
43
  return [] unless context.present?
44
44
 
45
- # Find events with matching request_id in context
46
45
  request_id = context["request_id"] || context[:request_id]
47
- return [] unless request_id
48
-
49
- event_id = event.respond_to?(:id) ? event.id : event[:id]
46
+ occurred_at = event[:occurred_at]
47
+ return [] unless request_id && occurred_at
50
48
 
51
49
  RailsEventViewer.events
52
50
  .with_context("request_id", request_id)
51
+ .since(occurred_at - 1.hour)
52
+ .until(occurred_at + 1.hour)
53
53
  .limit(11)
54
54
  .to_a
55
- .reject { |e| (e.respond_to?(:id) ? e.id : e[:id]) == event_id }
55
+ .reject { |related| related[:id] == event[:id] }
56
56
  .first(10)
57
57
  end
58
58
  end
@@ -1,27 +1,23 @@
1
1
  module RailsEventViewer
2
2
  module ApplicationHelper
3
- def nav_link_class(controller)
4
- base_classes = "rounded-md px-3 py-2 text-sm font-medium"
3
+ def nav_link(label, path, controller)
4
+ active = controller_name == controller
5
+ state_classes = active ? "bg-indigo-700 text-white" : "text-indigo-200 hover:bg-indigo-500 hover:text-white"
5
6
 
6
- if controller_name == controller
7
- "#{base_classes} bg-indigo-700 text-white"
8
- else
9
- "#{base_classes} text-indigo-200 hover:bg-indigo-500 hover:text-white"
10
- end
7
+ link_to label, path, class: "rounded-md px-3 py-2 text-sm font-medium #{state_classes}", aria: { current: ("page" if active) }
11
8
  end
12
9
 
13
- # Format event time - works with both Entry model and Hash
14
10
  def format_event_time(event)
15
- time = event_occurred_at(event)
11
+ time = event[:occurred_at]
16
12
  return "N/A" unless time
17
13
 
18
- time.strftime("%Y-%m-%d %H:%M:%S.%3N")
14
+ time.in_time_zone.strftime("%Y-%m-%d %H:%M:%S.%3N")
19
15
  end
20
16
 
21
17
  def time_ago_with_title(time)
22
18
  return "N/A" unless time
23
19
 
24
- content_tag(:span, time_ago_in_words(time) + " ago", title: time.strftime("%Y-%m-%d %H:%M:%S %Z"))
20
+ content_tag(:span, time_ago_in_words(time) + " ago", title: time.in_time_zone.strftime("%Y-%m-%d %H:%M:%S %Z"))
25
21
  end
26
22
 
27
23
  def truncate_json(json, length: 100)
@@ -29,73 +25,31 @@ module RailsEventViewer
29
25
  truncate(text, length: length)
30
26
  end
31
27
 
32
- def json_tree(data, depth: 0, max_depth: nil)
33
- return content_tag(:span, "null", class: "text-gray-400") if data.nil?
28
+ def json_tree(data)
29
+ return content_tag(:span, "null", class: "text-gray-500") if data.nil?
34
30
  return content_tag(:span, JSON.generate(data), class: json_value_class(data)) unless data.is_a?(Hash) || data.is_a?(Array)
35
- return content_tag(:span, collapsed_summary(data), class: "text-gray-400") if max_depth && depth >= max_depth
36
31
 
37
- if data.is_a?(Hash)
38
- render_hash_tree(data, depth, max_depth)
39
- else
40
- render_array_tree(data, depth, max_depth)
41
- end
32
+ data.is_a?(Hash) ? render_hash_tree(data) : render_array_tree(data)
42
33
  end
43
34
 
44
35
  def page_path(page)
45
36
  "#{request.path}?#{request.query_parameters.merge("page" => page.to_s).to_query}"
46
37
  end
47
38
 
48
- def timezone_options
49
- ActiveSupport::TimeZone.all.map { |tz| [tz.to_s, tz.name] }
50
- end
51
-
52
- # Helper to get attribute from either Entry model or Hash
53
- def event_attribute(event, attr)
54
- if event.respond_to?(attr)
55
- event.public_send(attr)
56
- elsif event.is_a?(Hash)
57
- event[attr] || event[attr.to_s]
58
- end
59
- end
60
-
61
- def event_id(event)
62
- event_attribute(event, :id)
63
- end
64
-
65
- def event_name(event)
66
- event_attribute(event, :name)
67
- end
68
-
69
39
  def event_payload(event)
70
- event_attribute(event, :payload) || {}
40
+ event[:payload] || {}
71
41
  end
72
42
 
73
43
  def event_tags(event)
74
- event_attribute(event, :tags) || {}
44
+ event[:tags] || {}
75
45
  end
76
46
 
77
47
  def event_context(event)
78
- event_attribute(event, :context) || {}
79
- end
80
-
81
- def event_occurred_at(event)
82
- event_attribute(event, :occurred_at)
83
- end
84
-
85
- def event_source_file(event)
86
- event_attribute(event, :source_file)
87
- end
88
-
89
- def event_source_line(event)
90
- event_attribute(event, :source_line)
91
- end
92
-
93
- def event_source_label(event)
94
- event_attribute(event, :source_label)
48
+ event[:context] || {}
95
49
  end
96
50
 
97
51
  def event_short_filepath(event)
98
- filepath = event_source_file(event)
52
+ filepath = event[:source_file]
99
53
  return nil unless filepath
100
54
 
101
55
  filepath.gsub(%r{.*/app/}, "app/")
@@ -130,35 +84,27 @@ module RailsEventViewer
130
84
  end
131
85
  end
132
86
 
133
- def collapsed_summary(data)
134
- if data.is_a?(Hash)
135
- data.empty? ? "{}" : "{#{data.size} #{"key".pluralize(data.size)}}"
136
- else
137
- data.empty? ? "[]" : "[#{data.size} #{"item".pluralize(data.size)}]"
138
- end
139
- end
140
-
141
- def render_hash_tree(hash, depth, max_depth)
142
- return content_tag(:span, "{}", class: "text-gray-400") if hash.empty?
87
+ def render_hash_tree(hash)
88
+ return content_tag(:span, "{}", class: "text-gray-500") if hash.empty?
143
89
 
144
90
  content_tag(:div, class: "pl-4") do
145
91
  hash.map do |key, value|
146
92
  content_tag(:div, class: "py-0.5") do
147
93
  content_tag(:span, "#{key}:", class: "text-gray-600 font-medium mr-2") +
148
- json_tree(value, depth: depth + 1, max_depth: max_depth)
94
+ json_tree(value)
149
95
  end
150
96
  end.join.html_safe
151
97
  end
152
98
  end
153
99
 
154
- def render_array_tree(array, depth, max_depth)
155
- return content_tag(:span, "[]", class: "text-gray-400") if array.empty?
100
+ def render_array_tree(array)
101
+ return content_tag(:span, "[]", class: "text-gray-500") if array.empty?
156
102
 
157
103
  content_tag(:div, class: "pl-4") do
158
104
  array.each_with_index.map do |value, index|
159
105
  content_tag(:div, class: "py-0.5") do
160
- content_tag(:span, "[#{index}]:", class: "text-gray-400 mr-2") +
161
- json_tree(value, depth: depth + 1, max_depth: max_depth)
106
+ content_tag(:span, "[#{index}]:", class: "text-gray-500 mr-2") +
107
+ json_tree(value)
162
108
  end
163
109
  end.join.html_safe
164
110
  end
@@ -4,31 +4,5 @@ module RailsEventViewer
4
4
 
5
5
  validates :name, presence: true
6
6
  validates :occurred_at, presence: true
7
-
8
- def source_location
9
- {
10
- filepath: source_file,
11
- lineno: source_line,
12
- label: source_label
13
- }.compact
14
- end
15
-
16
- def short_filepath
17
- return nil unless source_file
18
-
19
- source_file.gsub(%r{.*/app/}, "app/")
20
- end
21
-
22
- def tagged?
23
- tags.present? && tags.any?
24
- end
25
-
26
- def has_context?
27
- context.present? && context.any?
28
- end
29
-
30
- def tag(key)
31
- tags&.dig(key.to_s)
32
- end
33
7
  end
34
8
  end
@@ -31,5 +31,14 @@ module RailsEventViewer
31
31
  def next
32
32
  page + 1 if page < pages
33
33
  end
34
+
35
+ def window
36
+ numbers = [1, *(page - 2..page + 2), pages].select { |number| number.between?(1, pages) }.uniq.sort
37
+
38
+ numbers.each_with_object([]) do |number, items|
39
+ items << :gap if items.any? && number > items.last + 1
40
+ items << number
41
+ end
42
+ end
34
43
  end
35
44
  end
@@ -11,9 +11,8 @@
11
11
  "rails_event_viewer/chartjs-adapter-date-fns.bundle.min",
12
12
  "rails_event_viewer/chartkick.min",
13
13
  "rails_event_viewer/application",
14
+ defer: true,
14
15
  nonce: true %>
15
-
16
- <%= yield :head %>
17
16
  </head>
18
17
  <body class="h-full">
19
18
  <div class="min-h-full">
@@ -22,13 +21,13 @@
22
21
  <main class="py-6">
23
22
  <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
24
23
  <% if notice.present? %>
25
- <div class="mb-4 rounded-md bg-green-50 p-4">
24
+ <div class="mb-4 rounded-md bg-green-50 p-4" role="status">
26
25
  <p class="text-sm font-medium text-green-800"><%= notice %></p>
27
26
  </div>
28
27
  <% end %>
29
28
 
30
29
  <% if alert.present? %>
31
- <div class="mb-4 rounded-md bg-red-50 p-4">
30
+ <div class="mb-4 rounded-md bg-red-50 p-4" role="alert">
32
31
  <p class="text-sm font-medium text-red-800"><%= alert %></p>
33
32
  </div>
34
33
  <% end %>
@@ -8,10 +8,12 @@
8
8
  <%= form_with url: rails_event_viewer.analytics_overview_path, method: :get, class: "flex items-center space-x-3" do |f| %>
9
9
  <%= f.date_field :start_date,
10
10
  value: @date_range.begin.to_date,
11
+ aria: { label: "Start date" },
11
12
  class: "rounded-md border border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 py-2 px-3" %>
12
13
  <span class="text-gray-500">to</span>
13
14
  <%= f.date_field :end_date,
14
15
  value: @date_range.end.to_date,
16
+ aria: { label: "End date" },
15
17
  class: "rounded-md border border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 py-2 px-3" %>
16
18
  <%= f.submit "Apply",
17
19
  class: "bg-indigo-600 text-white px-5 py-2.5 rounded-md font-medium hover:bg-indigo-700 cursor-pointer" %>
@@ -41,7 +43,7 @@
41
43
  <!-- Charts -->
42
44
  <div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
43
45
  <div class="bg-white rounded-lg shadow p-6 overflow-hidden">
44
- <h3 class="text-lg font-medium text-gray-900 mb-4">Events Over Time</h3>
46
+ <h2 class="text-lg font-medium text-gray-900 mb-4">Events Over Time</h2>
45
47
  <div class="h-72 overflow-hidden">
46
48
  <% if @events_over_time.any? %>
47
49
  <%= line_chart @events_over_time,
@@ -54,7 +56,7 @@
54
56
  scales: { y: { beginAtZero: true } }
55
57
  } %>
56
58
  <% else %>
57
- <div class="flex items-center justify-center h-full text-gray-400">
59
+ <div class="flex items-center justify-center h-full text-gray-500">
58
60
  No data for the selected period
59
61
  </div>
60
62
  <% end %>
@@ -62,7 +64,7 @@
62
64
  </div>
63
65
 
64
66
  <div class="bg-white rounded-lg shadow p-6 overflow-hidden">
65
- <h3 class="text-lg font-medium text-gray-900 mb-4">Events by Type</h3>
67
+ <h2 class="text-lg font-medium text-gray-900 mb-4">Events by Type</h2>
66
68
  <div class="h-72 overflow-hidden">
67
69
  <% if @events_by_type.any? %>
68
70
  <%= bar_chart @events_by_type,
@@ -75,7 +77,7 @@
75
77
  plugins: { legend: { display: false } }
76
78
  } %>
77
79
  <% else %>
78
- <div class="flex items-center justify-center h-full text-gray-400">
80
+ <div class="flex items-center justify-center h-full text-gray-500">
79
81
  No data for the selected period
80
82
  </div>
81
83
  <% end %>
@@ -86,7 +88,7 @@
86
88
  <!-- Event type breakdown table -->
87
89
  <div class="bg-white rounded-lg shadow">
88
90
  <div class="px-6 py-4 border-b border-gray-200">
89
- <h3 class="text-lg font-medium text-gray-900">Event Type Breakdown</h3>
91
+ <h2 class="text-lg font-medium text-gray-900">Event Type Breakdown</h2>
90
92
  </div>
91
93
 
92
94
  <% if @events_by_type.any? %>
@@ -94,10 +96,10 @@
94
96
  <table class="min-w-full divide-y divide-gray-200">
95
97
  <thead class="bg-gray-50">
96
98
  <tr>
97
- <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Event Type</th>
98
- <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Count</th>
99
- <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Percentage</th>
100
- <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Distribution</th>
99
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Event Type</th>
100
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Count</th>
101
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Percentage</th>
102
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Distribution</th>
101
103
  </tr>
102
104
  </thead>
103
105
  <tbody class="divide-y divide-gray-200">
@@ -28,7 +28,7 @@
28
28
  <div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
29
29
  <!-- Events over time chart -->
30
30
  <div class="bg-white rounded-lg shadow p-6 overflow-hidden">
31
- <h3 class="text-lg font-medium text-gray-900 mb-4">Events (Last 24 Hours)</h3>
31
+ <h2 class="text-lg font-medium text-gray-900 mb-4">Events (Last 24 Hours)</h2>
32
32
  <div class="h-72 overflow-hidden">
33
33
  <% if @events_over_time.any? %>
34
34
  <%= line_chart @events_over_time,
@@ -38,7 +38,7 @@
38
38
  colors: ["#4F46E5"],
39
39
  library: { plugins: { legend: { display: false } } } %>
40
40
  <% else %>
41
- <div class="flex items-center justify-center h-full text-gray-400">
41
+ <div class="flex items-center justify-center h-full text-gray-500">
42
42
  No events in the last 24 hours
43
43
  </div>
44
44
  <% end %>
@@ -47,7 +47,7 @@
47
47
 
48
48
  <!-- Events by type chart -->
49
49
  <div class="bg-white rounded-lg shadow p-6 overflow-hidden">
50
- <h3 class="text-lg font-medium text-gray-900 mb-4">Events by Type</h3>
50
+ <h2 class="text-lg font-medium text-gray-900 mb-4">Events by Type</h2>
51
51
  <div class="h-72 overflow-hidden">
52
52
  <% if @events_by_type.any? %>
53
53
  <%= pie_chart @events_by_type,
@@ -57,7 +57,7 @@
57
57
  donut: true,
58
58
  colors: ["#4F46E5", "#10B981", "#F59E0B", "#EF4444", "#8B5CF6", "#EC4899", "#06B6D4", "#84CC16"] %>
59
59
  <% else %>
60
- <div class="flex items-center justify-center h-full text-gray-400">
60
+ <div class="flex items-center justify-center h-full text-gray-500">
61
61
  No events to display
62
62
  </div>
63
63
  <% end %>
@@ -69,7 +69,7 @@
69
69
  <!-- Recent events -->
70
70
  <div class="bg-white rounded-lg shadow">
71
71
  <div class="px-6 py-4 border-b border-gray-200 flex justify-between items-center">
72
- <h3 class="text-lg font-medium text-gray-900">Recent Events</h3>
72
+ <h2 class="text-lg font-medium text-gray-900">Recent Events</h2>
73
73
  <%= link_to "View all", rails_event_viewer.events_path, class: "text-indigo-600 hover:text-indigo-500 text-sm font-medium" %>
74
74
  </div>
75
75
 
@@ -78,23 +78,23 @@
78
78
  <table class="min-w-full divide-y divide-gray-200">
79
79
  <thead class="bg-gray-50">
80
80
  <tr>
81
- <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Event</th>
82
- <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Payload</th>
83
- <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Time</th>
81
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Event</th>
82
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Payload</th>
83
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Time</th>
84
84
  </tr>
85
85
  </thead>
86
86
  <tbody class="bg-white divide-y divide-gray-200">
87
87
  <% @recent_events.each do |event| %>
88
- <tr class="hover:bg-gray-50 cursor-pointer" data-event-viewer-href="<%= rails_event_viewer.event_path(event_id(event)) %>">
88
+ <tr class="hover:bg-gray-50 cursor-pointer" data-event-viewer-href="<%= rails_event_viewer.event_path(event[:id]) %>">
89
89
  <td class="px-6 py-4 whitespace-nowrap">
90
- <%= link_to event_name(event), rails_event_viewer.event_path(event_id(event)),
90
+ <%= link_to event[:name], rails_event_viewer.event_path(event[:id]),
91
91
  class: "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-indigo-100 text-indigo-800" %>
92
92
  </td>
93
93
  <td class="px-6 py-4 text-sm text-gray-500">
94
94
  <code class="text-xs bg-gray-100 px-2 py-1 rounded"><%= truncate_json(event_payload(event), length: 60) %></code>
95
95
  </td>
96
96
  <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
97
- <%= time_ago_with_title(event_occurred_at(event)) %>
97
+ <%= time_ago_with_title(event[:occurred_at]) %>
98
98
  </td>
99
99
  </tr>
100
100
  <% end %>
@@ -103,10 +103,10 @@
103
103
  </div>
104
104
  <% else %>
105
105
  <div class="px-6 py-12 text-center text-gray-500">
106
- <svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
106
+ <svg aria-hidden="true" class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
107
107
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
108
108
  </svg>
109
- <h3 class="mt-2 text-sm font-medium text-gray-900">No events yet</h3>
109
+ <h2 class="mt-2 text-sm font-medium text-gray-900">No events yet</h2>
110
110
  <p class="mt-1 text-sm text-gray-500">Events will appear here once they are captured.</p>
111
111
  </div>
112
112
  <% end %>
@@ -13,10 +13,10 @@
13
13
  <table class="min-w-full divide-y divide-gray-200">
14
14
  <thead class="bg-gray-50">
15
15
  <tr>
16
- <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Event Type</th>
17
- <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Count</th>
18
- <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Last Event</th>
19
- <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"></th>
16
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Event Type</th>
17
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Count</th>
18
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Last Event</th>
19
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"><span class="sr-only">Actions</span></th>
20
20
  </tr>
21
21
  </thead>
22
22
  <tbody class="bg-white divide-y divide-gray-200">
@@ -49,10 +49,10 @@
49
49
  </div>
50
50
  <% else %>
51
51
  <div class="bg-white rounded-lg shadow px-6 py-12 text-center text-gray-500">
52
- <svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
52
+ <svg aria-hidden="true" class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
53
53
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A2 2 0 013 12V7a4 4 0 014-4z" />
54
54
  </svg>
55
- <h3 class="mt-2 text-sm font-medium text-gray-900">No event types</h3>
55
+ <h2 class="mt-2 text-sm font-medium text-gray-900">No event types</h2>
56
56
  <p class="mt-1 text-sm text-gray-500">Event types will appear here once events are captured.</p>
57
57
  </div>
58
58
  <% end %>