sentiero 1.0.0.alpha1 → 1.0.0.beta3

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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +15 -13
  3. data/lib/sentiero/analytics/analyzer.rb +16 -4
  4. data/lib/sentiero/analytics/browser_event_discovery.rb +2 -2
  5. data/lib/sentiero/analytics/conversion_analyzer.rb +1 -1
  6. data/lib/sentiero/analytics/engagement_analyzer.rb +1 -1
  7. data/lib/sentiero/analytics/error_discovery.rb +9 -2
  8. data/lib/sentiero/analytics/exporter.rb +1 -1
  9. data/lib/sentiero/analytics/frustration_analyzer.rb +1 -1
  10. data/lib/sentiero/analytics/funnel_analyzer.rb +2 -2
  11. data/lib/sentiero/analytics/heatmap_analyzer.rb +3 -3
  12. data/lib/sentiero/analytics/page_report_analyzer.rb +2 -2
  13. data/lib/sentiero/analytics/scroll_depth_analyzer.rb +1 -1
  14. data/lib/sentiero/analytics/segmenter.rb +16 -2
  15. data/lib/sentiero/analytics/stats_aggregator/result_builder.rb +2 -0
  16. data/lib/sentiero/analytics/stats_aggregator.rb +45 -6
  17. data/lib/sentiero/analytics/web_vitals_analyzer.rb +2 -2
  18. data/lib/sentiero/configuration.rb +29 -0
  19. data/lib/sentiero/fingerprint/config.rb +40 -0
  20. data/lib/sentiero/fingerprint.rb +45 -11
  21. data/lib/sentiero/geo.rb +86 -0
  22. data/lib/sentiero/reporter.rb +2 -1
  23. data/lib/sentiero/store/session_store.rb +62 -3
  24. data/lib/sentiero/store.rb +7 -0
  25. data/lib/sentiero/stores/file.rb +5 -1
  26. data/lib/sentiero/stores/memory.rb +15 -3
  27. data/lib/sentiero/stores/redis.rb +17 -5
  28. data/lib/sentiero/stores/sqlite/payload_codec.rb +42 -0
  29. data/lib/sentiero/stores/sqlite/schema.rb +72 -6
  30. data/lib/sentiero/stores/sqlite.rb +216 -101
  31. data/lib/sentiero/version.rb +1 -1
  32. data/lib/sentiero/web/analytics_app.rb +10 -0
  33. data/lib/sentiero/web/assets/dashboard-YINQE5ZH.js +3 -0
  34. data/lib/sentiero/web/assets/manifest.json +4 -4
  35. data/lib/sentiero/web/assets/recorder-W2RCU5VM.js +78 -0
  36. data/lib/sentiero/web/assets/rrweb-player-d522d74c.js +201 -0
  37. data/lib/sentiero/web/assets/{style-d71e72fd.css → style-80e536e6.css} +1 -1
  38. data/lib/sentiero/web/body_reader.rb +21 -10
  39. data/lib/sentiero/web/dashboard_app.rb +77 -5
  40. data/lib/sentiero/web/errors_app.rb +14 -1
  41. data/lib/sentiero/web/events_app.rb +6 -1
  42. data/lib/sentiero/web/ingest_app.rb +1 -1
  43. data/lib/sentiero/web/templates/client_error_show.html.erb +6 -1
  44. data/lib/sentiero/web/templates/dashboard.html.erb +7 -0
  45. data/lib/sentiero/web/templates/maintenance.html.erb +50 -0
  46. data/lib/sentiero/web/templates/segments.html.erb +9 -0
  47. data/lib/sentiero/web/templates/sessions_index.html.erb +12 -4
  48. data/lib/sentiero/web/views/analytics_index_view.rb +7 -1
  49. data/lib/sentiero/web/views/client_error_show_view.rb +1 -1
  50. data/lib/sentiero/web/views/maintenance_view.rb +27 -0
  51. data/lib/sentiero/web/views/segments_view.rb +3 -2
  52. data/lib/sentiero/web/views/sessions_index_view.rb +6 -2
  53. data/lib/sentiero/web/views.rb +1 -0
  54. data/lib/sentiero.rb +1 -0
  55. metadata +10 -5
  56. data/lib/sentiero/web/assets/dashboard-JFYNHZZV.js +0 -3
  57. data/lib/sentiero/web/assets/recorder-SLLXSUUX.js +0 -71
  58. data/lib/sentiero/web/assets/rrweb-player-cd435a95.js +0 -126
@@ -1,2 +1,2 @@
1
1
  /*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */
2
- @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-leading:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid}}}@layer theme{:root,:host{--font-sans:"Inter", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;--font-mono:ui-monospace, "SF Mono", "Fira Code", "Fira Mono", "Roboto Mono", Menlo, monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-400:oklch(70.4% .191 22.216);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-900:oklch(39.6% .141 25.723);--color-blue-50:oklch(97% .014 254.604);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(42.4% .199 265.638);--color-indigo-50:oklch(96.2% .018 272.314);--color-indigo-400:oklch(67.3% .182 276.935);--color-indigo-700:oklch(45.7% .24 277.023);--color-indigo-900:oklch(35.9% .144 278.697);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-normal:0em;--tracking-wider:.05em;--leading-normal:1.5;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-surface-subtle:#f8f9fb;--color-border:#e2e5ea;--color-border-light:#eef0f3;--color-primary:#2563eb;--color-primary-hover:#1d4ed8;--color-primary-light:#eff6ff;--color-success:#059669;--color-success-bg:#ecfdf5;--color-warning:#d97706;--color-warning-bg:#fffbeb;--color-danger:#dc2626;--color-danger-bg:#fef2f2;--color-danger-hover:#b91c1c;--color-info:#0891b2;--color-info-bg:#ecfeff;--sidebar-w:220px}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}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;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}body{--tw-leading:var(--leading-normal);font-size:13px;line-height:var(--leading-normal);color:var(--color-gray-900);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:var(--font-sans);background:var(--color-surface-subtle);margin:0}*,:before,:after{border-color:var(--color-border)}a{color:var(--color-primary);text-decoration-line:none}a:hover{color:var(--color-primary-hover)}h1,h2,h3,h4,h5,h6{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight);color:var(--color-gray-900)}}@layer components{.btn{cursor:pointer;justify-content:center;align-items:center;gap:calc(var(--spacing) * 1.5);border-radius:var(--radius-md);border-style:var(--tw-border-style);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-leading:1;--tw-font-weight:var(--font-weight-medium);line-height:1;font-weight:var(--font-weight-medium);white-space:nowrap;font-family:var(--font-sans);border-width:1px;border-color:#0000;text-decoration-line:none;transition:all .12s;display:inline-flex;box-shadow:0 1px 2px #0000000d}.btn:focus-visible{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);outline-style:var(--tw-outline-style);outline-offset:1px;outline-width:2px;outline-color:var(--color-primary)}.btn-sm{padding-inline:calc(var(--spacing) * 2.5);padding-block:calc(var(--spacing) * 1.5);font-size:11px}.btn-xs{padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * 1);font-size:11px}.btn-primary{color:var(--color-white);background:var(--color-gray-900);border-color:var(--color-gray-900)}.btn-primary:hover{color:var(--color-white);background:var(--color-gray-800);border-color:var(--color-gray-800)}.btn-secondary{background-color:var(--color-white);color:var(--color-gray-600);border-color:var(--color-border);box-shadow:0 1px 2px #0000000a}.btn-secondary:hover{background-color:var(--color-gray-50);color:var(--color-gray-800);border-color:var(--color-gray-300)}.btn-danger{color:var(--color-white);background:var(--color-danger);border-color:var(--color-danger)}.btn-danger:hover{color:var(--color-white);background:var(--color-danger-hover);border-color:var(--color-danger-hover)}.btn-ghost-danger{color:var(--color-gray-400);border-color:var(--color-border);box-shadow:none;background-color:#0000}.btn-ghost-danger:hover{color:var(--color-danger);background:var(--color-danger-bg);border-color:var(--color-danger)}.btn-active{color:var(--color-white);background:var(--color-primary);border-color:var(--color-primary)}.btn-active:hover{color:var(--color-white);background:var(--color-primary-hover);border-color:var(--color-primary-hover)}.input,.select{border-radius:var(--radius-md);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 2.5);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-900);font-family:var(--font-sans);border:1px solid var(--color-border);transition:border-color .12s,box-shadow .12s;box-shadow:0 1px 2px #0000000a}.select{appearance:none;padding-right:calc(var(--spacing) * 7);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");background-position:right .4rem center;background-repeat:no-repeat;background-size:.65rem}.input:focus,.select:focus{--tw-outline-style:none;border-color:var(--color-primary);outline-style:none;box-shadow:0 0 0 3px #2563eb14}.input::placeholder{color:var(--color-gray-400)}.label{margin-bottom:calc(var(--spacing) * 1);--tw-font-weight:var(--font-weight-semibold);font-size:10px;font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider);color:var(--color-gray-400);text-transform:uppercase;display:block}.checkbox{height:calc(var(--spacing) * 3.5);width:calc(var(--spacing) * 3.5);cursor:pointer;border:1px solid var(--color-gray-300);accent-color:var(--color-primary);border-radius:.25rem}.badge{padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * .5);--tw-font-weight:var(--font-weight-medium);font-size:10px;font-weight:var(--font-weight-medium);white-space:nowrap;font-family:var(--font-sans);letter-spacing:.01em;border-radius:3.40282e38px;align-items:center;display:inline-flex}.badge-neutral{background-color:var(--color-gray-100);color:var(--color-gray-500)}.badge-primary{background:var(--color-primary-light);color:var(--color-primary)}.badge-warning{background:var(--color-warning-bg);color:var(--color-warning)}.badge-danger{background:var(--color-danger-bg);color:var(--color-danger)}.badge-success{background:var(--color-success-bg);color:var(--color-success)}.badge-info{background:var(--color-info-bg);color:var(--color-info)}.card{border-radius:var(--radius-lg);background-color:var(--color-white);border:1px solid var(--color-border);box-shadow:0 1px 3px #0000000a,0 1px 2px #00000005}.card-body{padding:calc(var(--spacing) * 5)}.data-table{text-align:left;width:100%;font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));border-collapse:separate;border-spacing:0}.data-table th{padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);--tw-font-weight:var(--font-weight-semibold);font-size:10px;font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider);white-space:nowrap;color:var(--color-gray-400);text-transform:uppercase;background:var(--color-surface-subtle);border-bottom:1px solid var(--color-border)}.data-table td{padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2.5);vertical-align:middle;color:var(--color-gray-600);border-bottom:1px solid var(--color-border-light)}.data-table tbody tr:last-child td{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.data-table tbody tr:hover td{background-color:#eff6ff66}@supports (color:color-mix(in lab, red, red)){.data-table tbody tr:hover td{background-color:color-mix(in oklab, var(--color-blue-50) 40%, transparent)}}.table-wrap{border-radius:var(--radius-lg);background-color:var(--color-white);border:1px solid var(--color-border);overflow:hidden;box-shadow:0 1px 3px #0000000a,0 1px 2px #00000005}.pagination{margin:calc(var(--spacing) * 0);gap:calc(var(--spacing) * 1);padding:calc(var(--spacing) * 0);list-style-type:none;display:flex}.page-link{border-radius:var(--radius-md);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-600);border:1px solid var(--color-border);text-decoration-line:none;display:block}.page-link:hover{background-color:var(--color-gray-50);color:var(--color-gray-800);border-color:var(--color-gray-300)}.page-disabled .page-link{pointer-events:none;background-color:var(--color-gray-50);color:var(--color-gray-300)}.alert-error{margin-bottom:calc(var(--spacing) * 3);border-radius:var(--radius-lg);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2.5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));background:var(--color-danger-bg);color:var(--color-danger);border:1px solid #fecaca}.tabs{margin-bottom:calc(var(--spacing) * 5);align-items:center;gap:calc(var(--spacing) * 6);border-bottom:1px solid var(--color-border);display:flex}.tab{align-items:center;gap:calc(var(--spacing) * 1.5);padding-bottom:calc(var(--spacing) * 2.5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);white-space:nowrap;color:var(--color-gray-500);border-bottom:2px solid #0000;margin-bottom:-1px;text-decoration-line:none;transition:color .12s,border-color .12s;display:inline-flex}.tab:hover{color:var(--color-gray-800);border-bottom-color:var(--color-gray-300)}.tab-active,.tab-active:hover{color:var(--color-primary);border-bottom-color:var(--color-primary)}.tab-count{padding-inline:calc(var(--spacing) * 1.5);padding-block:calc(var(--spacing) * .5);--tw-font-weight:var(--font-weight-semibold);font-size:10px;font-weight:var(--font-weight-semibold);--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);background:var(--color-gray-100);color:var(--color-gray-500);border-radius:3.40282e38px}.tab-active .tab-count{background:var(--color-primary-light);color:var(--color-primary)}.banner-warning{margin-bottom:calc(var(--spacing) * 4);align-items:flex-start;gap:calc(var(--spacing) * 2);border-radius:var(--radius-lg);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));background:var(--color-warning-bg);color:var(--color-warning);border:1px solid #fde68a;border-left:3px solid var(--color-warning);display:flex}.banner-warning strong{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}}@layer utilities{.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.my-4{margin-block:calc(var(--spacing) * 4)}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mr-1{margin-right:calc(var(--spacing) * 1)}.mb-0{margin-bottom:calc(var(--spacing) * 0)}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-1\.5{margin-bottom:calc(var(--spacing) * 1.5)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-5{margin-bottom:calc(var(--spacing) * 5)}.ml-1{margin-left:calc(var(--spacing) * 1)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-3{margin-left:calc(var(--spacing) * 3)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline\!{display:inline!important}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.h-2{height:calc(var(--spacing) * 2)}.h-3{height:calc(var(--spacing) * 3)}.h-4{height:calc(var(--spacing) * 4)}.w-3{width:calc(var(--spacing) * 3)}.w-8{width:calc(var(--spacing) * 8)}.w-16{width:calc(var(--spacing) * 16)}.w-20{width:calc(var(--spacing) * 20)}.w-24{width:calc(var(--spacing) * 24)}.w-32{width:calc(var(--spacing) * 32)}.w-36{width:calc(var(--spacing) * 36)}.w-40{width:calc(var(--spacing) * 40)}.w-56{width:calc(var(--spacing) * 56)}.w-full{width:100%}.w-px{width:1px}.max-w-0{max-width:calc(var(--spacing) * 0)}.max-w-full{max-width:100%}.max-w-xs{max-width:var(--container-xs)}.flex-1{flex:1}.shrink-0{flex-shrink:0}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.cursor-pointer{cursor:pointer}.resize{resize:both}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-\[100px_1fr\]{grid-template-columns:100px 1fr}.grid-cols-\[140px_1fr\]{grid-template-columns:140px 1fr}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.gap-1{gap:calc(var(--spacing) * 1)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}.gap-x-3{column-gap:calc(var(--spacing) * 3)}.gap-x-4{column-gap:calc(var(--spacing) * 4)}.gap-y-0\.5{row-gap:calc(var(--spacing) * .5)}.gap-y-2{row-gap:calc(var(--spacing) * 2)}.self-end{align-self:flex-end}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.rounded{border-radius:.25rem}.rounded-sm{border-radius:var(--radius-sm)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l-2{border-left-style:var(--tw-border-style);border-left-width:2px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-blue-400{border-color:var(--color-blue-400)}.border-gray-100{border-color:var(--color-gray-100)}.border-gray-200{border-color:var(--color-gray-200)}.border-indigo-400{border-color:var(--color-indigo-400)}.border-red-400{border-color:var(--color-red-400)}.bg-blue-500{background-color:var(--color-blue-500)}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-200{background-color:var(--color-gray-200)}.bg-indigo-50{background-color:var(--color-indigo-50)}.bg-red-50{background-color:var(--color-red-50)}.p-1{padding:calc(var(--spacing) * 1)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-6{padding-block:calc(var(--spacing) * 6)}.py-10{padding-block:calc(var(--spacing) * 10)}.pt-0\.5{padding-top:calc(var(--spacing) * .5)}.pb-1\.5{padding-bottom:calc(var(--spacing) * 1.5)}.pl-2{padding-left:calc(var(--spacing) * 2)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.align-middle{vertical-align:middle}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-normal{--tw-tracking:var(--tracking-normal);letter-spacing:var(--tracking-normal)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-blue-500{color:var(--color-blue-500)}.text-blue-600{color:var(--color-blue-600)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-800{color:var(--color-gray-800)}.text-gray-900{color:var(--color-gray-900)}.text-indigo-400{color:var(--color-indigo-400)}.text-indigo-700{color:var(--color-indigo-700)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.normal-case{text-transform:none}.uppercase{text-transform:uppercase}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.blur{--tw-blur:blur(8px);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,)}.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,)}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.select-all{-webkit-user-select:all;user-select:all}.select-none{-webkit-user-select:none;user-select:none}.last\:border-0:last-child{border-style:var(--tw-border-style);border-width:0}@media (hover:hover){.hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}.hover\:text-blue-700:hover{color:var(--color-blue-700)}.hover\:text-blue-800:hover{color:var(--color-blue-800)}.hover\:text-indigo-900:hover{color:var(--color-indigo-900)}.hover\:text-red-900:hover{color:var(--color-red-900)}.hover\:underline:hover{text-decoration-line:underline}}@media (min-width:40rem){.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\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}@media (min-width:48rem){.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (min-width:64rem){.lg\:col-span-2{grid-column:span 2/span 2}.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-6{grid-template-columns:repeat(6,minmax(0,1fr))}.lg\:grid-cols-\[1fr_280px\]{grid-template-columns:1fr 280px}}}.s-layout{min-height:100vh;display:flex}.s-sidebar{top:calc(var(--spacing) * 0);bottom:calc(var(--spacing) * 0);left:calc(var(--spacing) * 0);z-index:50;background-color:var(--color-white);padding-block:calc(var(--spacing) * 4);width:var(--sidebar-w);border-right:1px solid var(--color-border);flex-direction:column;display:flex;position:fixed}.s-sidebar-brand{margin-bottom:calc(var(--spacing) * 5);align-items:center;gap:calc(var(--spacing) * 2);padding-inline:calc(var(--spacing) * 4);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight);color:var(--color-gray-900);text-decoration-line:none;display:flex}.s-sidebar-brand:hover{color:var(--color-gray-900)}.s-sidebar-brand-logo{height:calc(var(--spacing) * 7);width:calc(var(--spacing) * 7);border-radius:var(--radius-md);flex-shrink:0}.s-sidebar-nav{margin:calc(var(--spacing) * 0);padding-inline:calc(var(--spacing) * 2.5);list-style-type:none}.s-nav-item{margin-bottom:calc(var(--spacing) * .5);align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 2.5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-500);padding-block:7px;text-decoration-line:none;transition:all .12s;display:flex}.s-nav-item:hover{background-color:var(--color-gray-100);color:var(--color-gray-800)}.s-nav-item.active{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);background:var(--color-primary-light);color:var(--color-primary)}.s-nav-icon{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);opacity:.6;flex-shrink:0}.s-nav-item.active .s-nav-icon{opacity:1}.s-main{padding-inline:calc(var(--spacing) * 8);padding-block:calc(var(--spacing) * 6);margin-left:var(--sidebar-w);max-width:calc(1400px + var(--sidebar-w));flex:1}.s-mobile-header{background-color:var(--color-white);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);border-bottom:1px solid var(--color-border);justify-content:space-between;align-items:center;display:none}.page-title{margin-bottom:calc(var(--spacing) * 4);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight);color:var(--color-gray-900)}.session-id{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-500);font-family:var(--font-mono)}.metadata-url{text-overflow:ellipsis;white-space:nowrap;max-width:200px;overflow:hidden}.window-switcher{align-items:center;gap:calc(var(--spacing) * 2.5);padding-block:calc(var(--spacing) * 2);display:flex}.window-switcher-label{--tw-font-weight:var(--font-weight-semibold);font-size:10px;font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider);white-space:nowrap;color:var(--color-gray-400);text-transform:uppercase}.window-overflow{display:inline-block;position:relative}.window-overflow summary{cursor:pointer;list-style-type:none}.window-overflow summary::-webkit-details-marker{display:none}.window-overflow-menu{top:100%;left:calc(var(--spacing) * 0);z-index:10;margin-top:calc(var(--spacing) * 1);border-radius:var(--radius-lg);background-color:var(--color-white);min-width:200px;padding:calc(var(--spacing) * 1);border:1px solid var(--color-border);position:absolute;box-shadow:0 8px 24px #0000001f,0 2px 6px #0000000a}.window-overflow-menu a{border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-600);text-decoration-line:none;display:block}.window-overflow-menu a:hover{background-color:var(--color-gray-50)}.window-overflow-menu a.active{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);background:var(--color-primary-light);color:var(--color-primary)}.player-frame{margin-top:calc(var(--spacing) * 2);border-radius:var(--radius-lg);background-color:var(--color-white);border:1px solid var(--color-border);overflow:hidden;box-shadow:0 1px 3px #0000000a,0 1px 2px #00000005}.player-frame .rr-player{--tw-shadow:0 0 #0000;max-width:100%;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);border-radius:0}.player-frame .rr-controller{background:var(--color-surface-subtle);border-top:1px solid var(--color-border-light);border-radius:0}#replayer{justify-content:center;display:flex;overflow:hidden}.click-overlay-container{pointer-events:none;inset:calc(var(--spacing) * 0);z-index:20;position:absolute}.click-dot{pointer-events:none;background:#dc35458c;border:1px solid #dc3545;border-radius:3.40282e38px;width:12px;height:12px;transition:transform .12s,background .12s;position:absolute;box-shadow:0 0 0 2px #dc354533}.click-dot:hover,.click-dot.active{background:#dc3545d9;transform:scale(1.4)}#event-markers{padding-inline:calc(var(--spacing) * 3);padding-top:calc(var(--spacing) * 2)}.marker-legend{margin-bottom:calc(var(--spacing) * 1);gap:calc(var(--spacing) * 3);color:var(--color-gray-400);font-size:10px;display:flex}.marker-legend-item{align-items:center;gap:calc(var(--spacing) * 1);display:flex}.marker-legend-dot{height:calc(var(--spacing) * 2);width:calc(var(--spacing) * 2);border-radius:3.40282e38px;display:inline-block}.marker-bar{margin-bottom:calc(var(--spacing) * 2);height:calc(var(--spacing) * 8);cursor:pointer;border-radius:var(--radius-md);background:var(--color-surface-subtle);border:1px solid var(--color-border);box-sizing:border-box;max-width:100%;position:relative;overflow:visible}.marker-track{pointer-events:none;right:calc(var(--spacing) * 2);left:calc(var(--spacing) * 2);background:var(--color-border);border-radius:3.40282e38px;height:2px;position:absolute;top:50%;transform:translateY(-50%)}.marker-playhead{pointer-events:none;top:calc(var(--spacing) * 0);bottom:calc(var(--spacing) * 0);z-index:10;background:var(--color-primary);border-radius:1px;width:2px;transition:left .15s linear;position:absolute;box-shadow:0 0 4px #2563eb4d}.marker-playhead:before{content:"";background:var(--color-primary);border-radius:50%;width:8px;height:8px;display:block;position:absolute;top:-3px;left:50%;transform:translate(-50%);box-shadow:0 0 0 2px #fff,0 1px 3px #00000026}.event-marker{height:calc(var(--spacing) * 2);width:calc(var(--spacing) * 2);cursor:pointer;opacity:.7;border-radius:3.40282e38px;transition:opacity .12s,transform .12s;position:absolute;top:50%;transform:translate(-50%,-50%)}.event-marker:hover{z-index:1;opacity:1;transform:translate(-50%,-50%)scale(1.6)}.event-marker.error-marker{z-index:2;height:calc(var(--spacing) * 2.5);width:calc(var(--spacing) * 2.5);opacity:1;box-shadow:0 0 4px #dc262666}.marker-gap{top:calc(var(--spacing) * 0);bottom:calc(var(--spacing) * 0);z-index:0;cursor:help;border-left:1px solid var(--color-gray-300);border-right:1px solid var(--color-gray-300);background:repeating-linear-gradient(-45deg, transparent, transparent 3px, var(--color-border) 3px, var(--color-border) 5px);position:absolute}.marker-group{z-index:3;cursor:pointer;position:absolute;top:50%;transform:translate(-50%,-50%)}.marker-group .event-marker{display:block;position:static;transform:none}.marker-group:hover .event-marker{transform:scale(1.4)}.marker-group-dot{opacity:.9;width:14px!important;height:14px!important}.marker-group-count{pointer-events:none;background-color:var(--color-gray-700);text-align:center;--tw-font-weight:var(--font-weight-bold);font-size:9px;font-weight:var(--font-weight-bold);color:var(--color-white);border-radius:3.40282e38px;min-width:14px;padding-inline:3px;line-height:14px;position:absolute;top:-8px;left:50%;transform:translate(-50%)}.marker-dropdown{z-index:50;max-width:var(--container-xs);border-radius:var(--radius-lg);background-color:var(--color-white);min-width:200px;padding:calc(var(--spacing) * 1);white-space:nowrap;border:1px solid var(--color-border);position:absolute;top:22px;left:50%;transform:translate(-50%);box-shadow:0 8px 24px #0000001f,0 2px 6px #0000000a}.marker-dropdown-item{cursor:pointer;align-items:center;gap:calc(var(--spacing) * 1.5);border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 2.5);padding-block:calc(var(--spacing) * 1.5);color:var(--color-gray-600);font-size:11px;display:flex}.marker-dropdown-item:hover{background-color:var(--color-gray-50)}.marker-dropdown-item.error-entry{background:var(--color-danger-bg)}.marker-dropdown-item.error-entry:hover{background:#fee2e2}#activity-sidebar{margin-top:calc(var(--spacing) * 2);border-radius:var(--radius-lg);background-color:var(--color-white);max-height:600px;padding:calc(var(--spacing) * 3);border:1px solid var(--color-border);overflow-y:auto;box-shadow:0 1px 3px #0000000a,0 1px 2px #00000005}#activity-sidebar h6{margin-bottom:calc(var(--spacing) * 2);align-items:center;gap:calc(var(--spacing) * 2);display:flex}.activity-entry{cursor:pointer;align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * 1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));border-bottom:1px solid var(--color-border-light);transition:background 80ms;display:flex}.activity-entry:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.activity-entry:hover{background-color:var(--color-gray-50)}.activity-entry.active{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);background:var(--color-primary-light)}.activity-entry.error-entry{background:var(--color-danger-bg);border-left:3px solid var(--color-danger)}.activity-entry.navigation-entry{background:var(--color-success-bg);border-left:3px solid var(--color-success)}.activity-entry.frustration-entry{background:#ff6b6b1a;border-left:3px solid #ff6b6b}.activity-entry.server-exception-entry{background:#b91c1c14;border-left:3px solid #b91c1c}.activity-entry.server-event-entry{background:#7c3aed14;border-left:3px solid #7c3aed}.activity-label-link{color:var(--color-blue-600);text-decoration-line:none}@media (hover:hover){.activity-label-link:hover{text-decoration-line:underline}}.activity-time{min-width:calc(var(--spacing) * 10);white-space:nowrap;color:var(--color-gray-400);font-size:10px;font-family:var(--font-mono)}.activity-dot{height:calc(var(--spacing) * 1.5);width:calc(var(--spacing) * 1.5);border-radius:3.40282e38px;flex-shrink:0;display:inline-block}.activity-label{text-overflow:ellipsis;white-space:nowrap;color:var(--color-gray-600);overflow:hidden}.activity-gap{margin-block:calc(var(--spacing) * .5);padding-block:calc(var(--spacing) * .5);text-align:center;color:var(--color-gray-400);border-top:1px dashed var(--color-border-light);border-bottom:1px dashed var(--color-border-light);background:repeating-linear-gradient(-45deg, transparent, transparent 4px, var(--color-surface-subtle) 4px, var(--color-surface-subtle) 6px);font-size:10px}.activity-wrapper{position:relative}.activity-detail{margin-left:calc(var(--spacing) * 2);padding-inline:calc(var(--spacing) * 2.5);padding-block:calc(var(--spacing) * 1.5);overflow-wrap:break-word;color:var(--color-gray-500);background:var(--color-surface-subtle);border-left:2px solid var(--color-border);border-radius:0 6px 6px 0;font-size:11px;display:none}.activity-wrapper:hover .activity-detail{display:block}.activity-detail-line{margin-bottom:calc(var(--spacing) * .5)}.activity-detail-line:last-child{margin-bottom:calc(var(--spacing) * 0)}.activity-detail-pre{margin-top:calc(var(--spacing) * .5);background-color:var(--color-gray-100);max-height:120px;padding:calc(var(--spacing) * 1.5);word-break:break-all;white-space:pre-wrap;font-size:10px;font-family:var(--font-mono);border-radius:.25rem;overflow-y:auto}.stats-chart{margin-top:calc(var(--spacing) * 3);align-items:flex-end;gap:calc(var(--spacing) * 1);height:180px;padding-top:calc(var(--spacing) * 4);padding-bottom:calc(var(--spacing) * 2);border-bottom:1px solid var(--color-border-light);display:flex}.stats-chart-col{min-width:calc(var(--spacing) * 0);flex-direction:column;flex:1;align-items:center;display:flex}.stats-chart-bar-wrapper{flex:1;justify-content:center;align-items:flex-end;width:100%;height:140px;display:flex}.stats-chart-bar{background:var(--color-primary);border-radius:4px 4px 0 0;width:60%;max-width:36px;min-height:2px;transition:height .3s,background .12s;position:relative}.stats-chart-bar:hover{background:var(--color-primary-hover)}.stats-chart-value{--tw-font-weight:var(--font-weight-semibold);font-size:10px;font-weight:var(--font-weight-semibold);white-space:nowrap;color:var(--color-gray-500);position:absolute;top:-16px;left:50%;transform:translate(-50%)}.stats-chart-label{margin-top:calc(var(--spacing) * 1);white-space:nowrap;color:var(--color-gray-400);font-size:10px}@media (max-width:768px){.s-sidebar{display:none}.s-mobile-header{display:flex}.s-main{margin-left:calc(var(--spacing) * 0);padding:calc(var(--spacing) * 4)}}details[open]>summary .group-open\:rotate-90{transform:rotate(90deg)}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-leading{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}
2
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-leading:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid}}}@layer theme{:root,:host{--font-sans:"Inter", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;--font-mono:ui-monospace, "SF Mono", "Fira Code", "Fira Mono", "Roboto Mono", Menlo, monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-400:oklch(70.4% .191 22.216);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-900:oklch(39.6% .141 25.723);--color-blue-50:oklch(97% .014 254.604);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(42.4% .199 265.638);--color-indigo-50:oklch(96.2% .018 272.314);--color-indigo-400:oklch(67.3% .182 276.935);--color-indigo-700:oklch(45.7% .24 277.023);--color-indigo-900:oklch(35.9% .144 278.697);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-normal:0em;--tracking-wider:.05em;--leading-normal:1.5;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-surface-subtle:#f8f9fb;--color-border:#e2e5ea;--color-border-light:#eef0f3;--color-primary:#2563eb;--color-primary-hover:#1d4ed8;--color-primary-light:#eff6ff;--color-success:#059669;--color-success-bg:#ecfdf5;--color-warning:#d97706;--color-warning-bg:#fffbeb;--color-danger:#dc2626;--color-danger-bg:#fef2f2;--color-danger-hover:#b91c1c;--color-info:#0891b2;--color-info-bg:#ecfeff;--sidebar-w:220px}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}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;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}body{--tw-leading:var(--leading-normal);font-size:13px;line-height:var(--leading-normal);color:var(--color-gray-900);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:var(--font-sans);background:var(--color-surface-subtle);margin:0}*,:before,:after{border-color:var(--color-border)}a{color:var(--color-primary);text-decoration-line:none}a:hover{color:var(--color-primary-hover)}h1,h2,h3,h4,h5,h6{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight);color:var(--color-gray-900)}}@layer components{.btn{cursor:pointer;justify-content:center;align-items:center;gap:calc(var(--spacing) * 1.5);border-radius:var(--radius-md);border-style:var(--tw-border-style);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-leading:1;--tw-font-weight:var(--font-weight-medium);line-height:1;font-weight:var(--font-weight-medium);white-space:nowrap;font-family:var(--font-sans);border-width:1px;border-color:#0000;text-decoration-line:none;transition:all .12s;display:inline-flex;box-shadow:0 1px 2px #0000000d}.btn:focus-visible{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);outline-style:var(--tw-outline-style);outline-offset:1px;outline-width:2px;outline-color:var(--color-primary)}.btn-sm{padding-inline:calc(var(--spacing) * 2.5);padding-block:calc(var(--spacing) * 1.5);font-size:11px}.btn-xs{padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * 1);font-size:11px}.btn-primary{color:var(--color-white);background:var(--color-gray-900);border-color:var(--color-gray-900)}.btn-primary:hover{color:var(--color-white);background:var(--color-gray-800);border-color:var(--color-gray-800)}.btn-secondary{background-color:var(--color-white);color:var(--color-gray-600);border-color:var(--color-border);box-shadow:0 1px 2px #0000000a}.btn-secondary:hover{background-color:var(--color-gray-50);color:var(--color-gray-800);border-color:var(--color-gray-300)}.btn-danger{color:var(--color-white);background:var(--color-danger);border-color:var(--color-danger)}.btn-danger:hover{color:var(--color-white);background:var(--color-danger-hover);border-color:var(--color-danger-hover)}.btn-ghost-danger{color:var(--color-gray-400);border-color:var(--color-border);box-shadow:none;background-color:#0000}.btn-ghost-danger:hover{color:var(--color-danger);background:var(--color-danger-bg);border-color:var(--color-danger)}.btn-active{color:var(--color-white);background:var(--color-primary);border-color:var(--color-primary)}.btn-active:hover{color:var(--color-white);background:var(--color-primary-hover);border-color:var(--color-primary-hover)}.input,.select{border-radius:var(--radius-md);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 2.5);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-900);font-family:var(--font-sans);border:1px solid var(--color-border);transition:border-color .12s,box-shadow .12s;box-shadow:0 1px 2px #0000000a}.select{appearance:none;padding-right:calc(var(--spacing) * 7);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");background-position:right .4rem center;background-repeat:no-repeat;background-size:.65rem}.input:focus,.select:focus{--tw-outline-style:none;border-color:var(--color-primary);outline-style:none;box-shadow:0 0 0 3px #2563eb14}.input::placeholder{color:var(--color-gray-400)}.label{margin-bottom:calc(var(--spacing) * 1);--tw-font-weight:var(--font-weight-semibold);font-size:10px;font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider);color:var(--color-gray-400);text-transform:uppercase;display:block}.checkbox{height:calc(var(--spacing) * 3.5);width:calc(var(--spacing) * 3.5);cursor:pointer;border:1px solid var(--color-gray-300);accent-color:var(--color-primary);border-radius:.25rem}.badge{padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * .5);--tw-font-weight:var(--font-weight-medium);font-size:10px;font-weight:var(--font-weight-medium);white-space:nowrap;font-family:var(--font-sans);letter-spacing:.01em;border-radius:3.40282e38px;align-items:center;display:inline-flex}.badge-neutral{background-color:var(--color-gray-100);color:var(--color-gray-500)}.badge-primary{background:var(--color-primary-light);color:var(--color-primary)}.badge-warning{background:var(--color-warning-bg);color:var(--color-warning)}.badge-danger{background:var(--color-danger-bg);color:var(--color-danger)}.badge-success{background:var(--color-success-bg);color:var(--color-success)}.badge-info{background:var(--color-info-bg);color:var(--color-info)}.card{border-radius:var(--radius-lg);background-color:var(--color-white);border:1px solid var(--color-border);box-shadow:0 1px 3px #0000000a,0 1px 2px #00000005}.card-body{padding:calc(var(--spacing) * 5)}.data-table{text-align:left;width:100%;font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));border-collapse:separate;border-spacing:0}.data-table th{padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);--tw-font-weight:var(--font-weight-semibold);font-size:10px;font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider);white-space:nowrap;color:var(--color-gray-400);text-transform:uppercase;background:var(--color-surface-subtle);border-bottom:1px solid var(--color-border)}.data-table td{padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2.5);vertical-align:middle;color:var(--color-gray-600);border-bottom:1px solid var(--color-border-light)}.data-table tbody tr:last-child td{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.data-table tbody tr:hover td{background-color:#eff6ff66}@supports (color:color-mix(in lab, red, red)){.data-table tbody tr:hover td{background-color:color-mix(in oklab, var(--color-blue-50) 40%, transparent)}}.table-wrap{border-radius:var(--radius-lg);background-color:var(--color-white);border:1px solid var(--color-border);overflow:hidden;box-shadow:0 1px 3px #0000000a,0 1px 2px #00000005}.pagination{margin:calc(var(--spacing) * 0);gap:calc(var(--spacing) * 1);padding:calc(var(--spacing) * 0);list-style-type:none;display:flex}.page-link{border-radius:var(--radius-md);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-600);border:1px solid var(--color-border);text-decoration-line:none;display:block}.page-link:hover{background-color:var(--color-gray-50);color:var(--color-gray-800);border-color:var(--color-gray-300)}.page-disabled .page-link{pointer-events:none;background-color:var(--color-gray-50);color:var(--color-gray-300)}.alert-error{margin-bottom:calc(var(--spacing) * 3);border-radius:var(--radius-lg);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2.5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));background:var(--color-danger-bg);color:var(--color-danger);border:1px solid #fecaca}.tabs{margin-bottom:calc(var(--spacing) * 5);align-items:center;gap:calc(var(--spacing) * 6);border-bottom:1px solid var(--color-border);display:flex}.tab{align-items:center;gap:calc(var(--spacing) * 1.5);padding-bottom:calc(var(--spacing) * 2.5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);white-space:nowrap;color:var(--color-gray-500);border-bottom:2px solid #0000;margin-bottom:-1px;text-decoration-line:none;transition:color .12s,border-color .12s;display:inline-flex}.tab:hover{color:var(--color-gray-800);border-bottom-color:var(--color-gray-300)}.tab-active,.tab-active:hover{color:var(--color-primary);border-bottom-color:var(--color-primary)}.tab-count{padding-inline:calc(var(--spacing) * 1.5);padding-block:calc(var(--spacing) * .5);--tw-font-weight:var(--font-weight-semibold);font-size:10px;font-weight:var(--font-weight-semibold);--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);background:var(--color-gray-100);color:var(--color-gray-500);border-radius:3.40282e38px}.tab-active .tab-count{background:var(--color-primary-light);color:var(--color-primary)}.banner-warning{margin-bottom:calc(var(--spacing) * 4);align-items:flex-start;gap:calc(var(--spacing) * 2);border-radius:var(--radius-lg);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));background:var(--color-warning-bg);color:var(--color-warning);border:1px solid #fde68a;border-left:3px solid var(--color-warning);display:flex}.banner-warning strong{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}}@layer utilities{.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.my-4{margin-block:calc(var(--spacing) * 4)}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mr-1{margin-right:calc(var(--spacing) * 1)}.mb-0{margin-bottom:calc(var(--spacing) * 0)}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-1\.5{margin-bottom:calc(var(--spacing) * 1.5)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-5{margin-bottom:calc(var(--spacing) * 5)}.ml-1{margin-left:calc(var(--spacing) * 1)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-3{margin-left:calc(var(--spacing) * 3)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline\!{display:inline!important}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.h-2{height:calc(var(--spacing) * 2)}.h-3{height:calc(var(--spacing) * 3)}.h-4{height:calc(var(--spacing) * 4)}.w-3{width:calc(var(--spacing) * 3)}.w-8{width:calc(var(--spacing) * 8)}.w-16{width:calc(var(--spacing) * 16)}.w-20{width:calc(var(--spacing) * 20)}.w-24{width:calc(var(--spacing) * 24)}.w-32{width:calc(var(--spacing) * 32)}.w-36{width:calc(var(--spacing) * 36)}.w-40{width:calc(var(--spacing) * 40)}.w-56{width:calc(var(--spacing) * 56)}.w-full{width:100%}.w-px{width:1px}.max-w-0{max-width:calc(var(--spacing) * 0)}.max-w-full{max-width:100%}.max-w-xs{max-width:var(--container-xs)}.flex-1{flex:1}.shrink-0{flex-shrink:0}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.cursor-pointer{cursor:pointer}.resize{resize:both}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-\[100px_1fr\]{grid-template-columns:100px 1fr}.grid-cols-\[140px_1fr\]{grid-template-columns:140px 1fr}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.gap-1{gap:calc(var(--spacing) * 1)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}.gap-x-3{column-gap:calc(var(--spacing) * 3)}.gap-x-4{column-gap:calc(var(--spacing) * 4)}.gap-y-0\.5{row-gap:calc(var(--spacing) * .5)}.gap-y-2{row-gap:calc(var(--spacing) * 2)}.self-end{align-self:flex-end}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.rounded{border-radius:.25rem}.rounded-sm{border-radius:var(--radius-sm)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l-2{border-left-style:var(--tw-border-style);border-left-width:2px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-blue-400{border-color:var(--color-blue-400)}.border-gray-100{border-color:var(--color-gray-100)}.border-gray-200{border-color:var(--color-gray-200)}.border-indigo-400{border-color:var(--color-indigo-400)}.border-red-400{border-color:var(--color-red-400)}.bg-blue-500{background-color:var(--color-blue-500)}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-200{background-color:var(--color-gray-200)}.bg-indigo-50{background-color:var(--color-indigo-50)}.bg-red-50{background-color:var(--color-red-50)}.p-1{padding:calc(var(--spacing) * 1)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-6{padding-block:calc(var(--spacing) * 6)}.py-10{padding-block:calc(var(--spacing) * 10)}.pt-0\.5{padding-top:calc(var(--spacing) * .5)}.pb-1\.5{padding-bottom:calc(var(--spacing) * 1.5)}.pl-2{padding-left:calc(var(--spacing) * 2)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.align-middle{vertical-align:middle}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-normal{--tw-tracking:var(--tracking-normal);letter-spacing:var(--tracking-normal)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-blue-500{color:var(--color-blue-500)}.text-blue-600{color:var(--color-blue-600)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-800{color:var(--color-gray-800)}.text-gray-900{color:var(--color-gray-900)}.text-indigo-400{color:var(--color-indigo-400)}.text-indigo-700{color:var(--color-indigo-700)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.normal-case{text-transform:none}.uppercase{text-transform:uppercase}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.underline{text-decoration-line:underline}.blur{--tw-blur:blur(8px);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,)}.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,)}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.select-all{-webkit-user-select:all;user-select:all}.select-none{-webkit-user-select:none;user-select:none}.last\:border-0:last-child{border-style:var(--tw-border-style);border-width:0}@media (hover:hover){.hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}.hover\:text-blue-700:hover{color:var(--color-blue-700)}.hover\:text-blue-800:hover{color:var(--color-blue-800)}.hover\:text-indigo-900:hover{color:var(--color-indigo-900)}.hover\:text-red-900:hover{color:var(--color-red-900)}.hover\:underline:hover{text-decoration-line:underline}}@media (min-width:40rem){.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\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}@media (min-width:48rem){.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (min-width:64rem){.lg\:col-span-2{grid-column:span 2/span 2}.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-6{grid-template-columns:repeat(6,minmax(0,1fr))}.lg\:grid-cols-\[1fr_280px\]{grid-template-columns:1fr 280px}}}.s-layout{min-height:100vh;display:flex}.s-sidebar{top:calc(var(--spacing) * 0);bottom:calc(var(--spacing) * 0);left:calc(var(--spacing) * 0);z-index:50;background-color:var(--color-white);padding-block:calc(var(--spacing) * 4);width:var(--sidebar-w);border-right:1px solid var(--color-border);flex-direction:column;display:flex;position:fixed}.s-sidebar-brand{margin-bottom:calc(var(--spacing) * 5);align-items:center;gap:calc(var(--spacing) * 2);padding-inline:calc(var(--spacing) * 4);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight);color:var(--color-gray-900);text-decoration-line:none;display:flex}.s-sidebar-brand:hover{color:var(--color-gray-900)}.s-sidebar-brand-logo{height:calc(var(--spacing) * 7);width:calc(var(--spacing) * 7);border-radius:var(--radius-md);flex-shrink:0}.s-sidebar-nav{margin:calc(var(--spacing) * 0);padding-inline:calc(var(--spacing) * 2.5);list-style-type:none}.s-nav-item{margin-bottom:calc(var(--spacing) * .5);align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 2.5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-500);padding-block:7px;text-decoration-line:none;transition:all .12s;display:flex}.s-nav-item:hover{background-color:var(--color-gray-100);color:var(--color-gray-800)}.s-nav-item.active{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);background:var(--color-primary-light);color:var(--color-primary)}.s-nav-icon{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);opacity:.6;flex-shrink:0}.s-nav-item.active .s-nav-icon{opacity:1}.s-main{padding-inline:calc(var(--spacing) * 8);padding-block:calc(var(--spacing) * 6);margin-left:var(--sidebar-w);max-width:calc(1400px + var(--sidebar-w));flex:1}.s-mobile-header{background-color:var(--color-white);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);border-bottom:1px solid var(--color-border);justify-content:space-between;align-items:center;display:none}.page-title{margin-bottom:calc(var(--spacing) * 4);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight);color:var(--color-gray-900)}.session-id{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-500);font-family:var(--font-mono)}.metadata-url{text-overflow:ellipsis;white-space:nowrap;max-width:200px;overflow:hidden}.window-switcher{align-items:center;gap:calc(var(--spacing) * 2.5);padding-block:calc(var(--spacing) * 2);display:flex}.window-switcher-label{--tw-font-weight:var(--font-weight-semibold);font-size:10px;font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider);white-space:nowrap;color:var(--color-gray-400);text-transform:uppercase}.window-overflow{display:inline-block;position:relative}.window-overflow summary{cursor:pointer;list-style-type:none}.window-overflow summary::-webkit-details-marker{display:none}.window-overflow-menu{top:100%;left:calc(var(--spacing) * 0);z-index:10;margin-top:calc(var(--spacing) * 1);border-radius:var(--radius-lg);background-color:var(--color-white);min-width:200px;padding:calc(var(--spacing) * 1);border:1px solid var(--color-border);position:absolute;box-shadow:0 8px 24px #0000001f,0 2px 6px #0000000a}.window-overflow-menu a{border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-600);text-decoration-line:none;display:block}.window-overflow-menu a:hover{background-color:var(--color-gray-50)}.window-overflow-menu a.active{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);background:var(--color-primary-light);color:var(--color-primary)}.player-frame{margin-top:calc(var(--spacing) * 2);border-radius:var(--radius-lg);background-color:var(--color-white);border:1px solid var(--color-border);overflow:hidden;box-shadow:0 1px 3px #0000000a,0 1px 2px #00000005}.player-frame .rr-player{--tw-shadow:0 0 #0000;max-width:100%;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);border-radius:0}.player-frame .rr-controller{background:var(--color-surface-subtle);border-top:1px solid var(--color-border-light);border-radius:0}#replayer{justify-content:center;display:flex;overflow:hidden}.click-overlay-container{pointer-events:none;inset:calc(var(--spacing) * 0);z-index:20;position:absolute}.click-dot{pointer-events:none;background:#dc35458c;border:1px solid #dc3545;border-radius:3.40282e38px;width:12px;height:12px;transition:transform .12s,background .12s;position:absolute;box-shadow:0 0 0 2px #dc354533}.click-dot:hover,.click-dot.active{background:#dc3545d9;transform:scale(1.4)}#event-markers{padding-inline:calc(var(--spacing) * 3);padding-top:calc(var(--spacing) * 2)}.marker-legend{margin-bottom:calc(var(--spacing) * 1);gap:calc(var(--spacing) * 3);color:var(--color-gray-400);font-size:10px;display:flex}.marker-legend-item{align-items:center;gap:calc(var(--spacing) * 1);display:flex}.marker-legend-dot{height:calc(var(--spacing) * 2);width:calc(var(--spacing) * 2);border-radius:3.40282e38px;display:inline-block}.marker-bar{margin-bottom:calc(var(--spacing) * 2);height:calc(var(--spacing) * 8);cursor:pointer;border-radius:var(--radius-md);background:var(--color-surface-subtle);border:1px solid var(--color-border);box-sizing:border-box;max-width:100%;position:relative;overflow:visible}.marker-track{pointer-events:none;right:calc(var(--spacing) * 2);left:calc(var(--spacing) * 2);background:var(--color-border);border-radius:3.40282e38px;height:2px;position:absolute;top:50%;transform:translateY(-50%)}.marker-playhead{pointer-events:none;top:calc(var(--spacing) * 0);bottom:calc(var(--spacing) * 0);z-index:10;background:var(--color-primary);border-radius:1px;width:2px;transition:left .15s linear;position:absolute;box-shadow:0 0 4px #2563eb4d}.marker-playhead:before{content:"";background:var(--color-primary);border-radius:50%;width:8px;height:8px;display:block;position:absolute;top:-3px;left:50%;transform:translate(-50%);box-shadow:0 0 0 2px #fff,0 1px 3px #00000026}.event-marker{height:calc(var(--spacing) * 2);width:calc(var(--spacing) * 2);cursor:pointer;opacity:.7;border-radius:3.40282e38px;transition:opacity .12s,transform .12s;position:absolute;top:50%;transform:translate(-50%,-50%)}.event-marker:hover{z-index:1;opacity:1;transform:translate(-50%,-50%)scale(1.6)}.event-marker.error-marker{z-index:2;height:calc(var(--spacing) * 2.5);width:calc(var(--spacing) * 2.5);opacity:1;box-shadow:0 0 4px #dc262666}.marker-gap{top:calc(var(--spacing) * 0);bottom:calc(var(--spacing) * 0);z-index:0;cursor:help;border-left:1px solid var(--color-gray-300);border-right:1px solid var(--color-gray-300);background:repeating-linear-gradient(-45deg, transparent, transparent 3px, var(--color-border) 3px, var(--color-border) 5px);position:absolute}.marker-group{z-index:3;cursor:pointer;position:absolute;top:50%;transform:translate(-50%,-50%)}.marker-group .event-marker{display:block;position:static;transform:none}.marker-group:hover .event-marker{transform:scale(1.4)}.marker-group-dot{opacity:.9;width:14px!important;height:14px!important}.marker-group-count{pointer-events:none;background-color:var(--color-gray-700);text-align:center;--tw-font-weight:var(--font-weight-bold);font-size:9px;font-weight:var(--font-weight-bold);color:var(--color-white);border-radius:3.40282e38px;min-width:14px;padding-inline:3px;line-height:14px;position:absolute;top:-8px;left:50%;transform:translate(-50%)}.marker-dropdown{z-index:50;max-width:var(--container-xs);border-radius:var(--radius-lg);background-color:var(--color-white);min-width:200px;padding:calc(var(--spacing) * 1);white-space:nowrap;border:1px solid var(--color-border);position:absolute;top:22px;left:50%;transform:translate(-50%);box-shadow:0 8px 24px #0000001f,0 2px 6px #0000000a}.marker-dropdown-item{cursor:pointer;align-items:center;gap:calc(var(--spacing) * 1.5);border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 2.5);padding-block:calc(var(--spacing) * 1.5);color:var(--color-gray-600);font-size:11px;display:flex}.marker-dropdown-item:hover{background-color:var(--color-gray-50)}.marker-dropdown-item.error-entry{background:var(--color-danger-bg)}.marker-dropdown-item.error-entry:hover{background:#fee2e2}#activity-sidebar{margin-top:calc(var(--spacing) * 2);border-radius:var(--radius-lg);background-color:var(--color-white);max-height:600px;padding:calc(var(--spacing) * 3);border:1px solid var(--color-border);overflow-y:auto;box-shadow:0 1px 3px #0000000a,0 1px 2px #00000005}#activity-sidebar h6{margin-bottom:calc(var(--spacing) * 2);align-items:center;gap:calc(var(--spacing) * 2);display:flex}.activity-entry{cursor:pointer;align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * 1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));border-bottom:1px solid var(--color-border-light);transition:background 80ms;display:flex}.activity-entry:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.activity-entry:hover{background-color:var(--color-gray-50)}.activity-entry.active{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);background:var(--color-primary-light)}.activity-entry.error-entry{background:var(--color-danger-bg);border-left:3px solid var(--color-danger)}.activity-entry.navigation-entry{background:var(--color-success-bg);border-left:3px solid var(--color-success)}.activity-entry.frustration-entry{background:#ff6b6b1a;border-left:3px solid #ff6b6b}.activity-entry.server-exception-entry{background:#b91c1c14;border-left:3px solid #b91c1c}.activity-entry.server-event-entry{background:#7c3aed14;border-left:3px solid #7c3aed}.activity-label-link{color:var(--color-blue-600);text-decoration-line:none}@media (hover:hover){.activity-label-link:hover{text-decoration-line:underline}}.activity-time{min-width:calc(var(--spacing) * 10);white-space:nowrap;color:var(--color-gray-400);font-size:10px;font-family:var(--font-mono)}.activity-dot{height:calc(var(--spacing) * 1.5);width:calc(var(--spacing) * 1.5);border-radius:3.40282e38px;flex-shrink:0;display:inline-block}.activity-label{text-overflow:ellipsis;white-space:nowrap;color:var(--color-gray-600);overflow:hidden}.activity-gap{margin-block:calc(var(--spacing) * .5);padding-block:calc(var(--spacing) * .5);text-align:center;color:var(--color-gray-400);border-top:1px dashed var(--color-border-light);border-bottom:1px dashed var(--color-border-light);background:repeating-linear-gradient(-45deg, transparent, transparent 4px, var(--color-surface-subtle) 4px, var(--color-surface-subtle) 6px);font-size:10px}.activity-wrapper{position:relative}.activity-detail{margin-left:calc(var(--spacing) * 2);padding-inline:calc(var(--spacing) * 2.5);padding-block:calc(var(--spacing) * 1.5);overflow-wrap:break-word;color:var(--color-gray-500);background:var(--color-surface-subtle);border-left:2px solid var(--color-border);border-radius:0 6px 6px 0;font-size:11px;display:none}.activity-wrapper:hover .activity-detail{display:block}.activity-detail-line{margin-bottom:calc(var(--spacing) * .5)}.activity-detail-line:last-child{margin-bottom:calc(var(--spacing) * 0)}.activity-detail-pre{margin-top:calc(var(--spacing) * .5);background-color:var(--color-gray-100);max-height:120px;padding:calc(var(--spacing) * 1.5);word-break:break-all;white-space:pre-wrap;font-size:10px;font-family:var(--font-mono);border-radius:.25rem;overflow-y:auto}.stats-chart{margin-top:calc(var(--spacing) * 3);align-items:flex-end;gap:calc(var(--spacing) * 1);height:180px;padding-top:calc(var(--spacing) * 4);padding-bottom:calc(var(--spacing) * 2);border-bottom:1px solid var(--color-border-light);display:flex}.stats-chart-col{min-width:calc(var(--spacing) * 0);flex-direction:column;flex:1;align-items:center;display:flex}.stats-chart-bar-wrapper{justify-content:center;align-items:flex-end;width:100%;height:140px;display:flex}.stats-chart-bar{background:var(--color-primary);border-radius:4px 4px 0 0;width:60%;max-width:36px;min-height:2px;transition:height .3s,background .12s;position:relative}.stats-chart-bar:hover{background:var(--color-primary-hover)}.stats-chart-value{--tw-font-weight:var(--font-weight-semibold);font-size:10px;font-weight:var(--font-weight-semibold);white-space:nowrap;color:var(--color-gray-500);position:absolute;top:-16px;left:50%;transform:translate(-50%)}.stats-chart-label{margin-top:calc(var(--spacing) * 1);white-space:nowrap;color:var(--color-gray-400);font-size:10px}@media (max-width:768px){.s-layout{flex-direction:column}.s-sidebar{display:none}.s-mobile-header{gap:calc(var(--spacing) * 2);flex-wrap:wrap;display:flex}.s-mobile-header nav{max-width:100%;overflow-x:auto}.s-main{margin-left:calc(var(--spacing) * 0);min-width:calc(var(--spacing) * 0);padding:calc(var(--spacing) * 4)}.tabs{overflow-x:auto}.s-main table{white-space:nowrap;max-width:100%;display:block;overflow-x:auto}.s-main form,.s-main .flex:has(>.shrink-0){flex-wrap:wrap}.s-main form .shrink-0{width:100%}.s-main .input[style],.s-main .select[style]{width:100%!important}}details[open]>summary .group-open\:rotate-90{transform:rotate(90deg)}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-leading{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}
@@ -5,10 +5,11 @@ require "zlib"
5
5
 
6
6
  module Sentiero
7
7
  module Web
8
- # Reads a request body (optionally gzip-encoded) with a hard 512KB cap on
9
- # BOTH the compressed and decompressed size, so a gzip bomb can't blow past
10
- # it. Shared by the two untrusted-input lanes (EventsApp, IngestApp), which
11
- # differ only in the response they build from the error symbol.
8
+ # Reads a request body (optionally gzip-encoded) with a hard cap (default
9
+ # 512KB, config.max_body_size) on BOTH the compressed and decompressed
10
+ # size, so a gzip bomb can't blow past it. Shared by the two
11
+ # untrusted-input lanes (EventsApp, IngestApp), which differ only in the
12
+ # response they build from the error symbol.
12
13
  module BodyReader
13
14
  MAX_BODY_SIZE = 524_288 # 512 KB
14
15
 
@@ -21,24 +22,34 @@ module Sentiero
21
22
  module_function
22
23
 
23
24
  # [body, nil] on success, or [nil, :too_large | :bad_gzip] on failure.
24
- def read(env)
25
- raw = env["rack.input"].read(MAX_BODY_SIZE + 1) || ""
25
+ def read(env, max_bytes: MAX_BODY_SIZE)
26
+ raw = env["rack.input"].read(max_bytes + 1) || ""
26
27
  env["rack.input"].rewind if env["rack.input"].respond_to?(:rewind)
27
- return [nil, :too_large] if raw.bytesize > MAX_BODY_SIZE
28
+ return [nil, :too_large] if raw.bytesize > max_bytes
28
29
 
29
- if env["HTTP_CONTENT_ENCODING"]&.downcase == "gzip"
30
+ if gzip?(env, raw)
30
31
  begin
31
32
  gz = Zlib::GzipReader.new(StringIO.new(raw))
32
- raw = gz.read(MAX_BODY_SIZE + 1) || ""
33
+ raw = gz.read(max_bytes + 1) || ""
33
34
  gz.close
34
35
  rescue Zlib::GzipFile::Error
35
36
  return [nil, :bad_gzip]
36
37
  end
37
- return [nil, :too_large] if raw.bytesize > MAX_BODY_SIZE
38
+ return [nil, :too_large] if raw.bytesize > max_bytes
38
39
  end
39
40
 
40
41
  [raw, nil]
41
42
  end
43
+
44
+ # Decompress when the client declared gzip or the body starts with the
45
+ # gzip magic number. The magic-byte path lets unload beacons ship
46
+ # compressed as text/plain without a Content-Encoding header, which would
47
+ # otherwise force a CORS preflight sendBeacon can't perform. JSON never
48
+ # begins with these bytes, so detection is unambiguous.
49
+ def gzip?(env, raw)
50
+ env["HTTP_CONTENT_ENCODING"]&.downcase == "gzip" ||
51
+ (raw.getbyte(0) == 0x1f && raw.getbyte(1) == 0x8b)
52
+ end
42
53
  end
43
54
  end
44
55
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "date"
3
4
  require_relative "base_app"
4
5
  require_relative "analytics_app"
5
6
  require_relative "monitoring_app"
@@ -57,6 +58,12 @@ module Sentiero
57
58
  else
58
59
  not_found
59
60
  end
61
+ when "/maintenance"
62
+ get_only(method) || handle_maintenance(env)
63
+ when "/maintenance/purge-before"
64
+ post_only(method) || handle_purge_before(env)
65
+ when "/maintenance/purge-expired"
66
+ post_only(method) || handle_purge_expired(env)
60
67
  when %r{\A/custom-events(?:/.*)?\z}
61
68
  Sentiero::Web::MonitoringApp.new.call(env)
62
69
  when %r{\A/issues(?:/.*)?\z}
@@ -95,6 +102,8 @@ module Sentiero
95
102
  search = params["search"]&.strip
96
103
  search = nil if search&.empty?
97
104
  has_errors_filter = params["has_errors"] == "true"
105
+ min_duration_s = positive_int_param(params["min_duration"])
106
+ min_events = positive_int_param(params["min_events"])
98
107
 
99
108
  sessions = fetch_sessions(
100
109
  has_errors_filter: has_errors_filter,
@@ -103,7 +112,9 @@ module Sentiero
103
112
  since: since,
104
113
  until_time: until_time,
105
114
  sort_by: sort_by,
106
- search: search
115
+ search: search,
116
+ min_duration_ms: min_duration_s && min_duration_s * 1000,
117
+ min_events: min_events
107
118
  )
108
119
 
109
120
  sessions, has_next = take_page(sessions, per_page)
@@ -119,11 +130,14 @@ module Sentiero
119
130
  sort_by: sort_by || "updated_at",
120
131
  since: params["since"] || "",
121
132
  until_param: params["until"] || "",
122
- has_errors: has_errors_filter
133
+ has_errors: has_errors_filter,
134
+ min_duration: min_duration_s&.to_s || "",
135
+ min_events: min_events&.to_s || ""
123
136
  ))
124
137
  end
125
138
 
126
- def fetch_sessions(has_errors_filter:, per_page:, offset:, since:, until_time:, sort_by:, search:)
139
+ def fetch_sessions(has_errors_filter:, per_page:, offset:, since:, until_time:, sort_by:, search:,
140
+ min_duration_ms: nil, min_events: nil)
127
141
  unless has_errors_filter
128
142
  return Sentiero.store.list_sessions(
129
143
  limit: per_page + 1,
@@ -131,7 +145,9 @@ module Sentiero
131
145
  since: since,
132
146
  until_time: until_time,
133
147
  sort_by: sort_by,
134
- search: search
148
+ search: search,
149
+ min_duration_ms: min_duration_ms,
150
+ min_events: min_events
135
151
  )
136
152
  end
137
153
 
@@ -145,12 +161,21 @@ module Sentiero
145
161
  since: since,
146
162
  until_time: until_time,
147
163
  sort_by: sort_by,
148
- search: search
164
+ search: search,
165
+ min_duration_ms: min_duration_ms,
166
+ min_events: min_events
149
167
  ).select { |s| s[:metadata] && s[:metadata]["has_errors"] }
150
168
 
151
169
  all_matching.slice(offset, per_page + 1) || []
152
170
  end
153
171
 
172
+ def positive_int_param(value)
173
+ return nil unless value.is_a?(String) && value.match?(/\A\d+\z/)
174
+
175
+ n = value.to_i
176
+ (n > 0) ? n : nil
177
+ end
178
+
154
179
  def handle_session_redirect(env, session_id)
155
180
  session = Sentiero.store.get_session(session_id)
156
181
  return [404, {"content-type" => "text/plain"}, ["Session not found"]] unless session
@@ -231,6 +256,53 @@ module Sentiero
231
256
  redirect("#{base_path(env)}/")
232
257
  end
233
258
  end
259
+
260
+ def handle_maintenance(env)
261
+ params = query_params(env)
262
+ purged = params["purged"]&.then { |v| v.match?(/\A\d+\z/) ? v.to_i : nil }
263
+ render_page(env, Views::MaintenanceView.new(
264
+ retention_period: Sentiero.configuration.retention_period,
265
+ purged: purged,
266
+ error: params["error"]
267
+ ), request_path: "/maintenance")
268
+ end
269
+
270
+ def handle_purge_before(env)
271
+ verify_csrf(env) || begin
272
+ post = ::Rack::Request.new(env).POST
273
+ return redirect(maintenance_path(env, error: "Tick the confirmation checkbox to purge.")) unless post["confirm"] == "1"
274
+
275
+ begin
276
+ date = Date.iso8601(post["date"].to_s)
277
+ rescue ArgumentError
278
+ return redirect(maintenance_path(env, error: "Enter a valid date."))
279
+ end
280
+
281
+ # Inclusive end of the chosen day, UTC.
282
+ until_time = Time.utc(date.year, date.month, date.day) + 86_400 - 0.001
283
+ audit!(env, action: :erase_where, dataset: date.iso8601)
284
+ count = Sentiero.erase_where(until_time: until_time)
285
+ redirect(maintenance_path(env, purged: count))
286
+ end
287
+ end
288
+
289
+ def handle_purge_expired(env)
290
+ verify_csrf(env) || begin
291
+ audit!(env, action: :purge)
292
+ count = Sentiero.purge_expired!
293
+ if count.nil?
294
+ redirect(maintenance_path(env, error: "retention_period is not set; nothing to purge."))
295
+ else
296
+ redirect(maintenance_path(env, purged: count))
297
+ end
298
+ end
299
+ end
300
+
301
+ def maintenance_path(env, purged: nil, error: nil)
302
+ qs = ::Rack::Utils.build_query({"purged" => purged, "error" => error}.compact)
303
+ path = "#{base_path(env)}/maintenance"
304
+ qs.empty? ? path : "#{path}?#{qs}"
305
+ end
234
306
  end
235
307
  end
236
308
  end
@@ -13,6 +13,7 @@ module Sentiero
13
13
  MAX_BACKTRACE_FRAMES = 100
14
14
  MAX_MESSAGE_LENGTH = 4000
15
15
  MAX_CONTEXT_BYTES = 16_384
16
+ PLATFORM_PATTERN = /\A[a-z0-9_-]{1,32}\z/i
16
17
 
17
18
  private
18
19
 
@@ -46,11 +47,14 @@ module Sentiero
46
47
  backtrace &&= backtrace.map { |frame| Redaction.redact_text(frame, redaction) }
47
48
 
48
49
  timestamp = numeric_timestamp(data["timestamp"])
50
+ platform = valid_platform(data["platform"])
51
+ normalizer = Sentiero.configuration.fingerprint.resolve(platform)
49
52
 
50
53
  fingerprint = Fingerprint.compute(
51
54
  exception_class: exception_class,
52
55
  backtrace: backtrace,
53
- project: project
56
+ project: project,
57
+ normalizer: normalizer
54
58
  )
55
59
 
56
60
  occurrence = {
@@ -67,6 +71,7 @@ module Sentiero
67
71
  end
68
72
  occurrence["session_id"] = session_id if session_id
69
73
  occurrence["window_id"] = window_id if window_id
74
+ occurrence["platform"] = platform if platform
70
75
 
71
76
  begin
72
77
  Sentiero.store.save_occurrence(occurrence)
@@ -77,6 +82,14 @@ module Sentiero
77
82
  json_response(200, {status: "ok", fingerprint: fingerprint})
78
83
  end
79
84
 
85
+ # Anything not matching PLATFORM_PATTERN is treated as absent (tier 1:
86
+ # default_platform's normalizer), not rejected — an unrecognized tag is a
87
+ # reporter quirk, not a malformed request. Downcased so storage/resolve
88
+ # use one canonical form.
89
+ def valid_platform(raw)
90
+ (raw.is_a?(String) && PLATFORM_PATTERN.match?(raw)) ? raw.downcase : nil
91
+ end
92
+
80
93
  def capped_context(context)
81
94
  (JSON.generate(context).bytesize <= MAX_CONTEXT_BYTES) ? context : {"_truncated" => true}
82
95
  end
@@ -6,6 +6,7 @@ require_relative "body_reader"
6
6
  require_relative "../analytics/events"
7
7
  require_relative "../redaction"
8
8
  require_relative "../store"
9
+ require_relative "../geo"
9
10
 
10
11
  module Sentiero
11
12
  module Web
@@ -34,7 +35,7 @@ module Sentiero
34
35
  # could still POST here.
35
36
  return dropped(env) if opted_out?(env) || gpc_signaled?(env)
36
37
 
37
- raw_body, error = BodyReader.read(env)
38
+ raw_body, error = BodyReader.read(env, max_bytes: Sentiero.configuration.max_body_size)
38
39
  if error
39
40
  status, message = BodyReader::ERRORS[error]
40
41
  return cors_error(env, status, message)
@@ -83,6 +84,10 @@ module Sentiero
83
84
  metadata = metadata.merge("has_errors" => true)
84
85
  end
85
86
 
87
+ # Server geo wins so a page can't forge its location via geo_* keys.
88
+ resolved_geo = Sentiero::Geo.resolve(env, Sentiero.configuration.geo_source, metadata)
89
+ metadata = resolved_geo.merge(metadata.except(*Sentiero::Geo::TARGET_KEYS))
90
+
86
91
  unless metadata.empty?
87
92
  metadata = Sentiero::Redaction.redact_metadata(metadata, Sentiero.configuration.redaction)
88
93
  Sentiero.store.save_metadata(session_id, metadata)
@@ -65,7 +65,7 @@ module Sentiero
65
65
  end
66
66
 
67
67
  def read_body(env)
68
- raw, error = BodyReader.read(env)
68
+ raw, error = BodyReader.read(env, max_bytes: Sentiero.configuration.max_body_size)
69
69
  return [raw, nil] unless error
70
70
 
71
71
  status, message = BodyReader::ERRORS[error]
@@ -23,6 +23,11 @@
23
23
  <div class="text-xs text-gray-600">
24
24
  <%= view.group[:last_seen_at] ? Time.at(view.group[:last_seen_at].to_f / 1000).utc.strftime("%b %d, %Y %H:%M:%S UTC") : "N/A" %>
25
25
  </div>
26
+
27
+ <% if view.group[:stack] -%>
28
+ <div class="text-[10px] font-medium text-gray-400 uppercase tracking-wider pt-0.5">Stack</div>
29
+ <pre class="text-[10px] text-gray-500 bg-gray-50 rounded p-2 overflow-x-auto whitespace-pre-wrap font-mono" data-client-error-stack><%= view.h(view.group[:stack]) %></pre>
30
+ <% end -%>
26
31
  </div>
27
32
  </div>
28
33
  </div>
@@ -36,7 +41,7 @@
36
41
  <% view.facet_chips.each do |title, counts| -%>
37
42
  <% next if counts.empty? -%>
38
43
  <div class="flex items-center gap-2 mb-1.5 flex-wrap">
39
- <span class="text-[10px] font-medium text-gray-400 uppercase tracking-wider w-16 shrink-0"><%= view.h(title) %></span>
44
+ <span class="text-[10px] font-medium text-gray-400 uppercase tracking-wider w-20 shrink-0"><%= view.h(title) %></span>
40
45
  <% counts.sort_by { |_value, count| -count }.first(8).each do |value, count| -%>
41
46
  <span class="badge badge-neutral"><%= view.h(value) %>&nbsp;<span class="tabular-nums">&times;<%= count %></span></span>
42
47
  <% end -%>
@@ -35,6 +35,12 @@
35
35
  Analytics
36
36
  </a>
37
37
  </li>
38
+ <li>
39
+ <a class="s-nav-item <%= request_path.start_with?('/maintenance') ? 'active' : '' %>" href="<%= view.h(view.base_path) %>/maintenance">
40
+ <svg class="s-nav-icon" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M11.42 15.17 17.25 21A2.652 2.652 0 0 0 21 17.25l-5.877-5.877M11.42 15.17l2.496-3.03c.317-.384.74-.626 1.208-.766M11.42 15.17l-4.655 5.653a2.548 2.548 0 1 1-3.586-3.586l6.837-5.63m5.108-.233c.55-.164 1.163-.188 1.743-.14a4.5 4.5 0 0 0 4.486-6.336l-3.276 3.277a3.004 3.004 0 0 1-2.25-2.25l3.276-3.276a4.5 4.5 0 0 0-6.336 4.486c.091 1.076-.071 2.264-.904 2.95l-.102.085m-1.745 1.437L5.909 7.5H4.5L2.25 3.75l1.5-1.5L7.5 4.5v1.409l4.26 4.26m-1.745 1.437 1.745-1.437m6.615 8.206L15.75 15.75M4.867 19.125h.008v.008h-.008v-.008Z" /></svg>
41
+ Maintenance
42
+ </a>
43
+ </li>
38
44
  </ul>
39
45
  </aside>
40
46
 
@@ -45,6 +51,7 @@
45
51
  <a class="s-nav-item <%= request_path.start_with?('/issues') ? 'active' : '' %>" href="<%= view.h(view.base_path) %>/issues">Errors</a>
46
52
  <a class="s-nav-item <%= request_path.start_with?('/custom-events') ? 'active' : '' %>" href="<%= view.h(view.base_path) %>/custom-events">Events</a>
47
53
  <a class="s-nav-item <%= request_path.start_with?('/analytics') ? 'active' : '' %>" href="<%= view.h(view.base_path) %>/analytics">Analytics</a>
54
+ <a class="s-nav-item <%= request_path.start_with?('/maintenance') ? 'active' : '' %>" href="<%= view.h(view.base_path) %>/maintenance">Maintenance</a>
48
55
  </nav>
49
56
  </header>
50
57
 
@@ -0,0 +1,50 @@
1
+ <h1 class="page-title">Maintenance</h1>
2
+
3
+ <% if view.purged %>
4
+ <div class="card mb-4 px-4 py-3 text-sm">Purged <%= view.purged %> session<%= view.purged == 1 ? "" : "s" %>.</div>
5
+ <% end %>
6
+ <% if view.error %>
7
+ <div class="card mb-4 px-4 py-3 text-sm text-red-600"><%= view.h(view.error) %></div>
8
+ <% end %>
9
+
10
+ <div class="card mb-4 px-4 py-3">
11
+ <h2 class="text-sm font-semibold mb-2">Purge recordings before a date</h2>
12
+ <p class="text-xs text-gray-500 mb-3">
13
+ Permanently deletes every session (and its error occurrences and tracked
14
+ events) last updated on or before the chosen date, UTC. This cannot be undone.
15
+ </p>
16
+ <form method="post" action="<%= view.h(view.base_path) %>/maintenance/purge-before">
17
+ <input type="hidden" name="csrf_token" value="<%= view.h(view.csrf_token) %>">
18
+ <div class="flex items-end gap-3">
19
+ <div>
20
+ <label for="date" class="label">Delete on/before</label>
21
+ <input type="date" name="date" id="date" class="input" required>
22
+ </div>
23
+ <label class="text-xs flex items-center gap-1.5 pb-1.5">
24
+ <input type="checkbox" name="confirm" value="1">
25
+ I understand this permanently deletes recordings
26
+ </label>
27
+ <button type="submit" class="btn btn-danger">Purge</button>
28
+ </div>
29
+ </form>
30
+ </div>
31
+
32
+ <div class="card mb-4 px-4 py-3">
33
+ <h2 class="text-sm font-semibold mb-2">Retention</h2>
34
+ <% if view.retention_period %>
35
+ <p class="text-xs text-gray-500 mb-3">
36
+ <code>retention_period</code> is <%= view.retention_period %> seconds
37
+ (<%= view.retention_days %> days). Scheduled purges should run from cron;
38
+ this button runs one now.
39
+ </p>
40
+ <form method="post" action="<%= view.h(view.base_path) %>/maintenance/purge-expired">
41
+ <input type="hidden" name="csrf_token" value="<%= view.h(view.csrf_token) %>">
42
+ <button type="submit" class="btn btn-danger">Run retention purge now</button>
43
+ </form>
44
+ <% else %>
45
+ <p class="text-xs text-gray-500">
46
+ <code>retention_period</code> is not set — recordings are kept forever.
47
+ See <a class="underline" href="https://www.sentiero.app/guide/privacy/#data-retention--purge">data retention</a>.
48
+ </p>
49
+ <% end %>
50
+ </div>
@@ -23,6 +23,15 @@
23
23
  <% end -%>
24
24
  </select>
25
25
  </div>
26
+ <div class="shrink-0">
27
+ <label for="country" class="label">Country</label>
28
+ <select name="country" id="country" class="select" style="width:8rem">
29
+ <option value="">Any</option>
30
+ <% (view.country_options | [view.filters[:country]].compact).each do |c| -%>
31
+ <option value="<%= view.h(c) %>" <%= view.filters[:country] == c ? "selected" : "" %>><%= view.h(c) %></option>
32
+ <% end -%>
33
+ </select>
34
+ </div>
26
35
  <div class="shrink-0">
27
36
  <label for="url_pattern" class="label">URL pattern</label>
28
37
  <input type="text" name="url_pattern" id="url_pattern" class="input" style="width:12rem" value="<%= view.h(view.filters[:url_pattern].to_s) %>" placeholder="substring or *glob*">