diff --git a/.github/skills/accessibility/SKILL.md b/.agents/skills/accessibility/SKILL.md similarity index 100% rename from .github/skills/accessibility/SKILL.md rename to .agents/skills/accessibility/SKILL.md diff --git a/.github/skills/backend-architecture/SKILL.md b/.agents/skills/backend-architecture/SKILL.md similarity index 100% rename from .github/skills/backend-architecture/SKILL.md rename to .agents/skills/backend-architecture/SKILL.md diff --git a/.github/skills/backend-testing/SKILL.md b/.agents/skills/backend-testing/SKILL.md similarity index 100% rename from .github/skills/backend-testing/SKILL.md rename to .agents/skills/backend-testing/SKILL.md diff --git a/.github/skills/dotnet-cli/SKILL.md b/.agents/skills/dotnet-cli/SKILL.md similarity index 100% rename from .github/skills/dotnet-cli/SKILL.md rename to .agents/skills/dotnet-cli/SKILL.md diff --git a/.github/skills/dotnet-conventions/SKILL.md b/.agents/skills/dotnet-conventions/SKILL.md similarity index 100% rename from .github/skills/dotnet-conventions/SKILL.md rename to .agents/skills/dotnet-conventions/SKILL.md diff --git a/.github/skills/e2e-testing/SKILL.md b/.agents/skills/e2e-testing/SKILL.md similarity index 100% rename from .github/skills/e2e-testing/SKILL.md rename to .agents/skills/e2e-testing/SKILL.md diff --git a/.github/skills/foundatio/SKILL.md b/.agents/skills/foundatio/SKILL.md similarity index 100% rename from .github/skills/foundatio/SKILL.md rename to .agents/skills/foundatio/SKILL.md diff --git a/.github/skills/frontend-architecture/SKILL.md b/.agents/skills/frontend-architecture/SKILL.md similarity index 100% rename from .github/skills/frontend-architecture/SKILL.md rename to .agents/skills/frontend-architecture/SKILL.md diff --git a/.github/skills/frontend-testing/SKILL.md b/.agents/skills/frontend-testing/SKILL.md similarity index 100% rename from .github/skills/frontend-testing/SKILL.md rename to .agents/skills/frontend-testing/SKILL.md diff --git a/.github/skills/security-principles/SKILL.md b/.agents/skills/security-principles/SKILL.md similarity index 100% rename from .github/skills/security-principles/SKILL.md rename to .agents/skills/security-principles/SKILL.md diff --git a/.github/skills/shadcn-svelte/SKILL.md b/.agents/skills/shadcn-svelte/SKILL.md similarity index 100% rename from .github/skills/shadcn-svelte/SKILL.md rename to .agents/skills/shadcn-svelte/SKILL.md diff --git a/.github/skills/storybook/SKILL.md b/.agents/skills/storybook/SKILL.md similarity index 100% rename from .github/skills/storybook/SKILL.md rename to .agents/skills/storybook/SKILL.md diff --git a/.github/skills/svelte-components/SKILL.md b/.agents/skills/svelte-components/SKILL.md similarity index 100% rename from .github/skills/svelte-components/SKILL.md rename to .agents/skills/svelte-components/SKILL.md diff --git a/.github/skills/tanstack-form/SKILL.md b/.agents/skills/tanstack-form/SKILL.md similarity index 100% rename from .github/skills/tanstack-form/SKILL.md rename to .agents/skills/tanstack-form/SKILL.md diff --git a/.github/skills/tanstack-query/SKILL.md b/.agents/skills/tanstack-query/SKILL.md similarity index 100% rename from .github/skills/tanstack-query/SKILL.md rename to .agents/skills/tanstack-query/SKILL.md diff --git a/.github/skills/typescript-conventions/SKILL.md b/.agents/skills/typescript-conventions/SKILL.md similarity index 100% rename from .github/skills/typescript-conventions/SKILL.md rename to .agents/skills/typescript-conventions/SKILL.md diff --git a/.github/.agents/skills/agent-browser/SKILL.md b/.github/.agents/skills/agent-browser/SKILL.md deleted file mode 100644 index b2f9b6ec2..000000000 --- a/.github/.agents/skills/agent-browser/SKILL.md +++ /dev/null @@ -1,181 +0,0 @@ ---- -name: agent-browser -description: Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling forms, clicking buttons, taking screenshots, extracting data, testing web apps, or automating any browser task. Triggers include requests to "open a website", "fill out a form", "click a button", "take a screenshot", "scrape data from a page", "test this web app", "login to a site", "automate browser actions", or any task requiring programmatic web interaction. -allowed-tools: Bash(agent-browser:*) ---- - -# Browser Automation with agent-browser - -## Core Workflow - -Every browser automation follows this pattern: - -1. **Navigate**: `agent-browser open ` -2. **Snapshot**: `agent-browser snapshot -i` (get element refs like `@e1`, `@e2`) -3. **Interact**: Use refs to click, fill, select -4. **Re-snapshot**: After navigation or DOM changes, get fresh refs - -```bash -agent-browser open https://example.com/form -agent-browser snapshot -i -# Output: @e1 [input type="email"], @e2 [input type="password"], @e3 [button] "Submit" - -agent-browser fill @e1 "user@example.com" -agent-browser fill @e2 "password123" -agent-browser click @e3 -agent-browser wait --load networkidle -agent-browser snapshot -i # Check result -``` - -## Essential Commands - -```bash -# Navigation -agent-browser open # Navigate (aliases: goto, navigate) -agent-browser close # Close browser - -# Snapshot -agent-browser snapshot -i # Interactive elements with refs (recommended) -agent-browser snapshot -s "#selector" # Scope to CSS selector - -# Interaction (use @refs from snapshot) -agent-browser click @e1 # Click element -agent-browser fill @e2 "text" # Clear and type text -agent-browser type @e2 "text" # Type without clearing -agent-browser select @e1 "option" # Select dropdown option -agent-browser check @e1 # Check checkbox -agent-browser press Enter # Press key -agent-browser scroll down 500 # Scroll page - -# Get information -agent-browser get text @e1 # Get element text -agent-browser get url # Get current URL -agent-browser get title # Get page title - -# Wait -agent-browser wait @e1 # Wait for element -agent-browser wait --load networkidle # Wait for network idle -agent-browser wait --url "**/page" # Wait for URL pattern -agent-browser wait 2000 # Wait milliseconds - -# Capture -agent-browser screenshot # Screenshot to temp dir -agent-browser screenshot --full # Full page screenshot -agent-browser pdf output.pdf # Save as PDF -``` - -## Common Patterns - -### Form Submission - -```bash -agent-browser open https://example.com/signup -agent-browser snapshot -i -agent-browser fill @e1 "Jane Doe" -agent-browser fill @e2 "jane@example.com" -agent-browser select @e3 "California" -agent-browser check @e4 -agent-browser click @e5 -agent-browser wait --load networkidle -``` - -### Authentication with State Persistence - -```bash -# Login once and save state -agent-browser open https://app.example.com/login -agent-browser snapshot -i -agent-browser fill @e1 "$USERNAME" -agent-browser fill @e2 "$PASSWORD" -agent-browser click @e3 -agent-browser wait --url "**/dashboard" -agent-browser state save auth.json - -# Reuse in future sessions -agent-browser state load auth.json -agent-browser open https://app.example.com/dashboard -``` - -### Data Extraction - -```bash -agent-browser open https://example.com/products -agent-browser snapshot -i -agent-browser get text @e5 # Get specific element text -agent-browser get text body > page.txt # Get all page text - -# JSON output for parsing -agent-browser snapshot -i --json -agent-browser get text @e1 --json -``` - -### Parallel Sessions - -```bash -agent-browser --session site1 open https://site-a.com -agent-browser --session site2 open https://site-b.com - -agent-browser --session site1 snapshot -i -agent-browser --session site2 snapshot -i - -agent-browser session list -``` - -### Visual Browser (Debugging) - -```bash -agent-browser --headed open https://example.com -agent-browser highlight @e1 # Highlight element -agent-browser record start demo.webm # Record session -``` - -## Ref Lifecycle (Important) - -Refs (`@e1`, `@e2`, etc.) are invalidated when the page changes. Always re-snapshot after: - -- Clicking links or buttons that navigate -- Form submissions -- Dynamic content loading (dropdowns, modals) - -```bash -agent-browser click @e5 # Navigates to new page -agent-browser snapshot -i # MUST re-snapshot -agent-browser click @e1 # Use new refs -``` - -## Semantic Locators (Alternative to Refs) - -When refs are unavailable or unreliable, use semantic locators: - -```bash -agent-browser find text "Sign In" click -agent-browser find label "Email" fill "user@test.com" -agent-browser find role button click --name "Submit" -agent-browser find placeholder "Search" type "query" -agent-browser find testid "submit-btn" click -``` - -## Deep-Dive Documentation - -| Reference | When to Use | -|-----------|-------------| -| [references/commands.md](references/commands.md) | Full command reference with all options | -| [references/snapshot-refs.md](references/snapshot-refs.md) | Ref lifecycle, invalidation rules, troubleshooting | -| [references/session-management.md](references/session-management.md) | Parallel sessions, state persistence, concurrent scraping | -| [references/authentication.md](references/authentication.md) | Login flows, OAuth, 2FA handling, state reuse | -| [references/video-recording.md](references/video-recording.md) | Recording workflows for debugging and documentation | -| [references/proxy-support.md](references/proxy-support.md) | Proxy configuration, geo-testing, rotating proxies | - -## Ready-to-Use Templates - -| Template | Description | -|----------|-------------| -| [templates/form-automation.sh](templates/form-automation.sh) | Form filling with validation | -| [templates/authenticated-session.sh](templates/authenticated-session.sh) | Login once, reuse state | -| [templates/capture-workflow.sh](templates/capture-workflow.sh) | Content extraction with screenshots | - -```bash -./templates/form-automation.sh https://example.com/form -./templates/authenticated-session.sh https://app.example.com/login -./templates/capture-workflow.sh https://example.com ./output -``` diff --git a/.github/.agents/skills/agent-browser/references/authentication.md b/.github/.agents/skills/agent-browser/references/authentication.md deleted file mode 100644 index 12ef5e41b..000000000 --- a/.github/.agents/skills/agent-browser/references/authentication.md +++ /dev/null @@ -1,202 +0,0 @@ -# Authentication Patterns - -Login flows, session persistence, OAuth, 2FA, and authenticated browsing. - -**Related**: [session-management.md](session-management.md) for state persistence details, [SKILL.md](../SKILL.md) for quick start. - -## Contents - -- [Basic Login Flow](#basic-login-flow) -- [Saving Authentication State](#saving-authentication-state) -- [Restoring Authentication](#restoring-authentication) -- [OAuth / SSO Flows](#oauth--sso-flows) -- [Two-Factor Authentication](#two-factor-authentication) -- [HTTP Basic Auth](#http-basic-auth) -- [Cookie-Based Auth](#cookie-based-auth) -- [Token Refresh Handling](#token-refresh-handling) -- [Security Best Practices](#security-best-practices) - -## Basic Login Flow - -```bash -# Navigate to login page -agent-browser open https://app.example.com/login -agent-browser wait --load networkidle - -# Get form elements -agent-browser snapshot -i -# Output: @e1 [input type="email"], @e2 [input type="password"], @e3 [button] "Sign In" - -# Fill credentials -agent-browser fill @e1 "user@example.com" -agent-browser fill @e2 "password123" - -# Submit -agent-browser click @e3 -agent-browser wait --load networkidle - -# Verify login succeeded -agent-browser get url # Should be dashboard, not login -``` - -## Saving Authentication State - -After logging in, save state for reuse: - -```bash -# Login first (see above) -agent-browser open https://app.example.com/login -agent-browser snapshot -i -agent-browser fill @e1 "user@example.com" -agent-browser fill @e2 "password123" -agent-browser click @e3 -agent-browser wait --url "**/dashboard" - -# Save authenticated state -agent-browser state save ./auth-state.json -``` - -## Restoring Authentication - -Skip login by loading saved state: - -```bash -# Load saved auth state -agent-browser state load ./auth-state.json - -# Navigate directly to protected page -agent-browser open https://app.example.com/dashboard - -# Verify authenticated -agent-browser snapshot -i -``` - -## OAuth / SSO Flows - -For OAuth redirects: - -```bash -# Start OAuth flow -agent-browser open https://app.example.com/auth/google - -# Handle redirects automatically -agent-browser wait --url "**/accounts.google.com**" -agent-browser snapshot -i - -# Fill Google credentials -agent-browser fill @e1 "user@gmail.com" -agent-browser click @e2 # Next button -agent-browser wait 2000 -agent-browser snapshot -i -agent-browser fill @e3 "password" -agent-browser click @e4 # Sign in - -# Wait for redirect back -agent-browser wait --url "**/app.example.com**" -agent-browser state save ./oauth-state.json -``` - -## Two-Factor Authentication - -Handle 2FA with manual intervention: - -```bash -# Login with credentials -agent-browser open https://app.example.com/login --headed # Show browser -agent-browser snapshot -i -agent-browser fill @e1 "user@example.com" -agent-browser fill @e2 "password123" -agent-browser click @e3 - -# Wait for user to complete 2FA manually -echo "Complete 2FA in the browser window..." -agent-browser wait --url "**/dashboard" --timeout 120000 - -# Save state after 2FA -agent-browser state save ./2fa-state.json -``` - -## HTTP Basic Auth - -For sites using HTTP Basic Authentication: - -```bash -# Set credentials before navigation -agent-browser set credentials username password - -# Navigate to protected resource -agent-browser open https://protected.example.com/api -``` - -## Cookie-Based Auth - -Manually set authentication cookies: - -```bash -# Set auth cookie -agent-browser cookies set session_token "abc123xyz" - -# Navigate to protected page -agent-browser open https://app.example.com/dashboard -``` - -## Token Refresh Handling - -For sessions with expiring tokens: - -```bash -#!/bin/bash -# Wrapper that handles token refresh - -STATE_FILE="./auth-state.json" - -# Try loading existing state -if [[ -f "$STATE_FILE" ]]; then - agent-browser state load "$STATE_FILE" - agent-browser open https://app.example.com/dashboard - - # Check if session is still valid - URL=$(agent-browser get url) - if [[ "$URL" == *"/login"* ]]; then - echo "Session expired, re-authenticating..." - # Perform fresh login - agent-browser snapshot -i - agent-browser fill @e1 "$USERNAME" - agent-browser fill @e2 "$PASSWORD" - agent-browser click @e3 - agent-browser wait --url "**/dashboard" - agent-browser state save "$STATE_FILE" - fi -else - # First-time login - agent-browser open https://app.example.com/login - # ... login flow ... -fi -``` - -## Security Best Practices - -1. **Never commit state files** - They contain session tokens - ```bash - echo "*.auth-state.json" >> .gitignore - ``` - -2. **Use environment variables for credentials** - ```bash - agent-browser fill @e1 "$APP_USERNAME" - agent-browser fill @e2 "$APP_PASSWORD" - ``` - -3. **Clean up after automation** - ```bash - agent-browser cookies clear - rm -f ./auth-state.json - ``` - -4. **Use short-lived sessions for CI/CD** - ```bash - # Don't persist state in CI - agent-browser open https://app.example.com/login - # ... login and perform actions ... - agent-browser close # Session ends, nothing persisted - ``` diff --git a/.github/.agents/skills/agent-browser/references/commands.md b/.github/.agents/skills/agent-browser/references/commands.md deleted file mode 100644 index 8744accf7..000000000 --- a/.github/.agents/skills/agent-browser/references/commands.md +++ /dev/null @@ -1,259 +0,0 @@ -# Command Reference - -Complete reference for all agent-browser commands. For quick start and common patterns, see SKILL.md. - -## Navigation - -```bash -agent-browser open # Navigate to URL (aliases: goto, navigate) - # Supports: https://, http://, file://, about:, data:// - # Auto-prepends https:// if no protocol given -agent-browser back # Go back -agent-browser forward # Go forward -agent-browser reload # Reload page -agent-browser close # Close browser (aliases: quit, exit) -agent-browser connect 9222 # Connect to browser via CDP port -``` - -## Snapshot (page analysis) - -```bash -agent-browser snapshot # Full accessibility tree -agent-browser snapshot -i # Interactive elements only (recommended) -agent-browser snapshot -c # Compact output -agent-browser snapshot -d 3 # Limit depth to 3 -agent-browser snapshot -s "#main" # Scope to CSS selector -``` - -## Interactions (use @refs from snapshot) - -```bash -agent-browser click @e1 # Click -agent-browser dblclick @e1 # Double-click -agent-browser focus @e1 # Focus element -agent-browser fill @e2 "text" # Clear and type -agent-browser type @e2 "text" # Type without clearing -agent-browser press Enter # Press key (alias: key) -agent-browser press Control+a # Key combination -agent-browser keydown Shift # Hold key down -agent-browser keyup Shift # Release key -agent-browser hover @e1 # Hover -agent-browser check @e1 # Check checkbox -agent-browser uncheck @e1 # Uncheck checkbox -agent-browser select @e1 "value" # Select dropdown option -agent-browser select @e1 "a" "b" # Select multiple options -agent-browser scroll down 500 # Scroll page (default: down 300px) -agent-browser scrollintoview @e1 # Scroll element into view (alias: scrollinto) -agent-browser drag @e1 @e2 # Drag and drop -agent-browser upload @e1 file.pdf # Upload files -``` - -## Get Information - -```bash -agent-browser get text @e1 # Get element text -agent-browser get html @e1 # Get innerHTML -agent-browser get value @e1 # Get input value -agent-browser get attr @e1 href # Get attribute -agent-browser get title # Get page title -agent-browser get url # Get current URL -agent-browser get count ".item" # Count matching elements -agent-browser get box @e1 # Get bounding box -agent-browser get styles @e1 # Get computed styles (font, color, bg, etc.) -``` - -## Check State - -```bash -agent-browser is visible @e1 # Check if visible -agent-browser is enabled @e1 # Check if enabled -agent-browser is checked @e1 # Check if checked -``` - -## Screenshots and PDF - -```bash -agent-browser screenshot # Save to temporary directory -agent-browser screenshot path.png # Save to specific path -agent-browser screenshot --full # Full page -agent-browser pdf output.pdf # Save as PDF -``` - -## Video Recording - -```bash -agent-browser record start ./demo.webm # Start recording -agent-browser click @e1 # Perform actions -agent-browser record stop # Stop and save video -agent-browser record restart ./take2.webm # Stop current + start new -``` - -## Wait - -```bash -agent-browser wait @e1 # Wait for element -agent-browser wait 2000 # Wait milliseconds -agent-browser wait --text "Success" # Wait for text (or -t) -agent-browser wait --url "**/dashboard" # Wait for URL pattern (or -u) -agent-browser wait --load networkidle # Wait for network idle (or -l) -agent-browser wait --fn "window.ready" # Wait for JS condition (or -f) -``` - -## Mouse Control - -```bash -agent-browser mouse move 100 200 # Move mouse -agent-browser mouse down left # Press button -agent-browser mouse up left # Release button -agent-browser mouse wheel 100 # Scroll wheel -``` - -## Semantic Locators (alternative to refs) - -```bash -agent-browser find role button click --name "Submit" -agent-browser find text "Sign In" click -agent-browser find text "Sign In" click --exact # Exact match only -agent-browser find label "Email" fill "user@test.com" -agent-browser find placeholder "Search" type "query" -agent-browser find alt "Logo" click -agent-browser find title "Close" click -agent-browser find testid "submit-btn" click -agent-browser find first ".item" click -agent-browser find last ".item" click -agent-browser find nth 2 "a" hover -``` - -## Browser Settings - -```bash -agent-browser set viewport 1920 1080 # Set viewport size -agent-browser set device "iPhone 14" # Emulate device -agent-browser set geo 37.7749 -122.4194 # Set geolocation (alias: geolocation) -agent-browser set offline on # Toggle offline mode -agent-browser set headers '{"X-Key":"v"}' # Extra HTTP headers -agent-browser set credentials user pass # HTTP basic auth (alias: auth) -agent-browser set media dark # Emulate color scheme -agent-browser set media light reduced-motion # Light mode + reduced motion -``` - -## Cookies and Storage - -```bash -agent-browser cookies # Get all cookies -agent-browser cookies set name value # Set cookie -agent-browser cookies clear # Clear cookies -agent-browser storage local # Get all localStorage -agent-browser storage local key # Get specific key -agent-browser storage local set k v # Set value -agent-browser storage local clear # Clear all -``` - -## Network - -```bash -agent-browser network route # Intercept requests -agent-browser network route --abort # Block requests -agent-browser network route --body '{}' # Mock response -agent-browser network unroute [url] # Remove routes -agent-browser network requests # View tracked requests -agent-browser network requests --filter api # Filter requests -``` - -## Tabs and Windows - -```bash -agent-browser tab # List tabs -agent-browser tab new [url] # New tab -agent-browser tab 2 # Switch to tab by index -agent-browser tab close # Close current tab -agent-browser tab close 2 # Close tab by index -agent-browser window new # New window -``` - -## Frames - -```bash -agent-browser frame "#iframe" # Switch to iframe -agent-browser frame main # Back to main frame -``` - -## Dialogs - -```bash -agent-browser dialog accept [text] # Accept dialog -agent-browser dialog dismiss # Dismiss dialog -``` - -## JavaScript - -```bash -agent-browser eval "document.title" # Simple expressions only -agent-browser eval -b "" # Any JavaScript (base64 encoded) -agent-browser eval --stdin # Read script from stdin -``` - -Use `-b`/`--base64` or `--stdin` for reliable execution. Shell escaping with nested quotes and special characters is error-prone. - -```bash -# Base64 encode your script, then: -agent-browser eval -b "ZG9jdW1lbnQucXVlcnlTZWxlY3RvcignW3NyYyo9Il9uZXh0Il0nKQ==" - -# Or use stdin with heredoc for multiline scripts: -cat <<'EOF' | agent-browser eval --stdin -const links = document.querySelectorAll('a'); -Array.from(links).map(a => a.href); -EOF -``` - -## State Management - -```bash -agent-browser state save auth.json # Save cookies, storage, auth state -agent-browser state load auth.json # Restore saved state -``` - -## Global Options - -```bash -agent-browser --session ... # Isolated browser session -agent-browser --json ... # JSON output for parsing -agent-browser --headed ... # Show browser window (not headless) -agent-browser --full ... # Full page screenshot (-f) -agent-browser --cdp ... # Connect via Chrome DevTools Protocol -agent-browser -p ... # Cloud browser provider (--provider) -agent-browser --proxy ... # Use proxy server -agent-browser --headers ... # HTTP headers scoped to URL's origin -agent-browser --executable-path

# Custom browser executable -agent-browser --extension ... # Load browser extension (repeatable) -agent-browser --ignore-https-errors # Ignore SSL certificate errors -agent-browser --help # Show help (-h) -agent-browser --version # Show version (-V) -agent-browser --help # Show detailed help for a command -``` - -## Debugging - -```bash -agent-browser --headed open example.com # Show browser window -agent-browser --cdp 9222 snapshot # Connect via CDP port -agent-browser connect 9222 # Alternative: connect command -agent-browser console # View console messages -agent-browser console --clear # Clear console -agent-browser errors # View page errors -agent-browser errors --clear # Clear errors -agent-browser highlight @e1 # Highlight element -agent-browser trace start # Start recording trace -agent-browser trace stop trace.zip # Stop and save trace -``` - -## Environment Variables - -```bash -AGENT_BROWSER_SESSION="mysession" # Default session name -AGENT_BROWSER_EXECUTABLE_PATH="/path/chrome" # Custom browser path -AGENT_BROWSER_EXTENSIONS="/ext1,/ext2" # Comma-separated extension paths -AGENT_BROWSER_PROVIDER="browserbase" # Cloud browser provider -AGENT_BROWSER_STREAM_PORT="9223" # WebSocket streaming port -AGENT_BROWSER_HOME="/path/to/agent-browser" # Custom install location -``` diff --git a/.github/.agents/skills/agent-browser/references/proxy-support.md b/.github/.agents/skills/agent-browser/references/proxy-support.md deleted file mode 100644 index 05cc9d538..000000000 --- a/.github/.agents/skills/agent-browser/references/proxy-support.md +++ /dev/null @@ -1,188 +0,0 @@ -# Proxy Support - -Proxy configuration for geo-testing, rate limiting avoidance, and corporate environments. - -**Related**: [commands.md](commands.md) for global options, [SKILL.md](../SKILL.md) for quick start. - -## Contents - -- [Basic Proxy Configuration](#basic-proxy-configuration) -- [Authenticated Proxy](#authenticated-proxy) -- [SOCKS Proxy](#socks-proxy) -- [Proxy Bypass](#proxy-bypass) -- [Common Use Cases](#common-use-cases) -- [Verifying Proxy Connection](#verifying-proxy-connection) -- [Troubleshooting](#troubleshooting) -- [Best Practices](#best-practices) - -## Basic Proxy Configuration - -Set proxy via environment variable before starting: - -```bash -# HTTP proxy -export HTTP_PROXY="http://proxy.example.com:8080" -agent-browser open https://example.com - -# HTTPS proxy -export HTTPS_PROXY="https://proxy.example.com:8080" -agent-browser open https://example.com - -# Both -export HTTP_PROXY="http://proxy.example.com:8080" -export HTTPS_PROXY="http://proxy.example.com:8080" -agent-browser open https://example.com -``` - -## Authenticated Proxy - -For proxies requiring authentication: - -```bash -# Include credentials in URL -export HTTP_PROXY="http://username:password@proxy.example.com:8080" -agent-browser open https://example.com -``` - -## SOCKS Proxy - -```bash -# SOCKS5 proxy -export ALL_PROXY="socks5://proxy.example.com:1080" -agent-browser open https://example.com - -# SOCKS5 with auth -export ALL_PROXY="socks5://user:pass@proxy.example.com:1080" -agent-browser open https://example.com -``` - -## Proxy Bypass - -Skip proxy for specific domains: - -```bash -# Bypass proxy for local addresses -export NO_PROXY="localhost,127.0.0.1,.internal.company.com" -agent-browser open https://internal.company.com # Direct connection -agent-browser open https://external.com # Via proxy -``` - -## Common Use Cases - -### Geo-Location Testing - -```bash -#!/bin/bash -# Test site from different regions using geo-located proxies - -PROXIES=( - "http://us-proxy.example.com:8080" - "http://eu-proxy.example.com:8080" - "http://asia-proxy.example.com:8080" -) - -for proxy in "${PROXIES[@]}"; do - export HTTP_PROXY="$proxy" - export HTTPS_PROXY="$proxy" - - region=$(echo "$proxy" | grep -oP '^\w+-\w+') - echo "Testing from: $region" - - agent-browser --session "$region" open https://example.com - agent-browser --session "$region" screenshot "./screenshots/$region.png" - agent-browser --session "$region" close -done -``` - -### Rotating Proxies for Scraping - -```bash -#!/bin/bash -# Rotate through proxy list to avoid rate limiting - -PROXY_LIST=( - "http://proxy1.example.com:8080" - "http://proxy2.example.com:8080" - "http://proxy3.example.com:8080" -) - -URLS=( - "https://site.com/page1" - "https://site.com/page2" - "https://site.com/page3" -) - -for i in "${!URLS[@]}"; do - proxy_index=$((i % ${#PROXY_LIST[@]})) - export HTTP_PROXY="${PROXY_LIST[$proxy_index]}" - export HTTPS_PROXY="${PROXY_LIST[$proxy_index]}" - - agent-browser open "${URLS[$i]}" - agent-browser get text body > "output-$i.txt" - agent-browser close - - sleep 1 # Polite delay -done -``` - -### Corporate Network Access - -```bash -#!/bin/bash -# Access internal sites via corporate proxy - -export HTTP_PROXY="http://corpproxy.company.com:8080" -export HTTPS_PROXY="http://corpproxy.company.com:8080" -export NO_PROXY="localhost,127.0.0.1,.company.com" - -# External sites go through proxy -agent-browser open https://external-vendor.com - -# Internal sites bypass proxy -agent-browser open https://intranet.company.com -``` - -## Verifying Proxy Connection - -```bash -# Check your apparent IP -agent-browser open https://httpbin.org/ip -agent-browser get text body -# Should show proxy's IP, not your real IP -``` - -## Troubleshooting - -### Proxy Connection Failed - -```bash -# Test proxy connectivity first -curl -x http://proxy.example.com:8080 https://httpbin.org/ip - -# Check if proxy requires auth -export HTTP_PROXY="http://user:pass@proxy.example.com:8080" -``` - -### SSL/TLS Errors Through Proxy - -Some proxies perform SSL inspection. If you encounter certificate errors: - -```bash -# For testing only - not recommended for production -agent-browser open https://example.com --ignore-https-errors -``` - -### Slow Performance - -```bash -# Use proxy only when necessary -export NO_PROXY="*.cdn.com,*.static.com" # Direct CDN access -``` - -## Best Practices - -1. **Use environment variables** - Don't hardcode proxy credentials -2. **Set NO_PROXY appropriately** - Avoid routing local traffic through proxy -3. **Test proxy before automation** - Verify connectivity with simple requests -4. **Handle proxy failures gracefully** - Implement retry logic for unstable proxies -5. **Rotate proxies for large scraping jobs** - Distribute load and avoid bans diff --git a/.github/.agents/skills/agent-browser/references/session-management.md b/.github/.agents/skills/agent-browser/references/session-management.md deleted file mode 100644 index bb5312dbd..000000000 --- a/.github/.agents/skills/agent-browser/references/session-management.md +++ /dev/null @@ -1,193 +0,0 @@ -# Session Management - -Multiple isolated browser sessions with state persistence and concurrent browsing. - -**Related**: [authentication.md](authentication.md) for login patterns, [SKILL.md](../SKILL.md) for quick start. - -## Contents - -- [Named Sessions](#named-sessions) -- [Session Isolation Properties](#session-isolation-properties) -- [Session State Persistence](#session-state-persistence) -- [Common Patterns](#common-patterns) -- [Default Session](#default-session) -- [Session Cleanup](#session-cleanup) -- [Best Practices](#best-practices) - -## Named Sessions - -Use `--session` flag to isolate browser contexts: - -```bash -# Session 1: Authentication flow -agent-browser --session auth open https://app.example.com/login - -# Session 2: Public browsing (separate cookies, storage) -agent-browser --session public open https://example.com - -# Commands are isolated by session -agent-browser --session auth fill @e1 "user@example.com" -agent-browser --session public get text body -``` - -## Session Isolation Properties - -Each session has independent: -- Cookies -- LocalStorage / SessionStorage -- IndexedDB -- Cache -- Browsing history -- Open tabs - -## Session State Persistence - -### Save Session State - -```bash -# Save cookies, storage, and auth state -agent-browser state save /path/to/auth-state.json -``` - -### Load Session State - -```bash -# Restore saved state -agent-browser state load /path/to/auth-state.json - -# Continue with authenticated session -agent-browser open https://app.example.com/dashboard -``` - -### State File Contents - -```json -{ - "cookies": [...], - "localStorage": {...}, - "sessionStorage": {...}, - "origins": [...] -} -``` - -## Common Patterns - -### Authenticated Session Reuse - -```bash -#!/bin/bash -# Save login state once, reuse many times - -STATE_FILE="/tmp/auth-state.json" - -# Check if we have saved state -if [[ -f "$STATE_FILE" ]]; then - agent-browser state load "$STATE_FILE" - agent-browser open https://app.example.com/dashboard -else - # Perform login - agent-browser open https://app.example.com/login - agent-browser snapshot -i - agent-browser fill @e1 "$USERNAME" - agent-browser fill @e2 "$PASSWORD" - agent-browser click @e3 - agent-browser wait --load networkidle - - # Save for future use - agent-browser state save "$STATE_FILE" -fi -``` - -### Concurrent Scraping - -```bash -#!/bin/bash -# Scrape multiple sites concurrently - -# Start all sessions -agent-browser --session site1 open https://site1.com & -agent-browser --session site2 open https://site2.com & -agent-browser --session site3 open https://site3.com & -wait - -# Extract from each -agent-browser --session site1 get text body > site1.txt -agent-browser --session site2 get text body > site2.txt -agent-browser --session site3 get text body > site3.txt - -# Cleanup -agent-browser --session site1 close -agent-browser --session site2 close -agent-browser --session site3 close -``` - -### A/B Testing Sessions - -```bash -# Test different user experiences -agent-browser --session variant-a open "https://app.com?variant=a" -agent-browser --session variant-b open "https://app.com?variant=b" - -# Compare -agent-browser --session variant-a screenshot /tmp/variant-a.png -agent-browser --session variant-b screenshot /tmp/variant-b.png -``` - -## Default Session - -When `--session` is omitted, commands use the default session: - -```bash -# These use the same default session -agent-browser open https://example.com -agent-browser snapshot -i -agent-browser close # Closes default session -``` - -## Session Cleanup - -```bash -# Close specific session -agent-browser --session auth close - -# List active sessions -agent-browser session list -``` - -## Best Practices - -### 1. Name Sessions Semantically - -```bash -# GOOD: Clear purpose -agent-browser --session github-auth open https://github.com -agent-browser --session docs-scrape open https://docs.example.com - -# AVOID: Generic names -agent-browser --session s1 open https://github.com -``` - -### 2. Always Clean Up - -```bash -# Close sessions when done -agent-browser --session auth close -agent-browser --session scrape close -``` - -### 3. Handle State Files Securely - -```bash -# Don't commit state files (contain auth tokens!) -echo "*.auth-state.json" >> .gitignore - -# Delete after use -rm /tmp/auth-state.json -``` - -### 4. Timeout Long Sessions - -```bash -# Set timeout for automated scripts -timeout 60 agent-browser --session long-task get text body -``` diff --git a/.github/.agents/skills/agent-browser/references/snapshot-refs.md b/.github/.agents/skills/agent-browser/references/snapshot-refs.md deleted file mode 100644 index c13d53a89..000000000 --- a/.github/.agents/skills/agent-browser/references/snapshot-refs.md +++ /dev/null @@ -1,194 +0,0 @@ -# Snapshot and Refs - -Compact element references that reduce context usage dramatically for AI agents. - -**Related**: [commands.md](commands.md) for full command reference, [SKILL.md](../SKILL.md) for quick start. - -## Contents - -- [How Refs Work](#how-refs-work) -- [Snapshot Command](#the-snapshot-command) -- [Using Refs](#using-refs) -- [Ref Lifecycle](#ref-lifecycle) -- [Best Practices](#best-practices) -- [Ref Notation Details](#ref-notation-details) -- [Troubleshooting](#troubleshooting) - -## How Refs Work - -Traditional approach: -``` -Full DOM/HTML → AI parses → CSS selector → Action (~3000-5000 tokens) -``` - -agent-browser approach: -``` -Compact snapshot → @refs assigned → Direct interaction (~200-400 tokens) -``` - -## The Snapshot Command - -```bash -# Basic snapshot (shows page structure) -agent-browser snapshot - -# Interactive snapshot (-i flag) - RECOMMENDED -agent-browser snapshot -i -``` - -### Snapshot Output Format - -``` -Page: Example Site - Home -URL: https://example.com - -@e1 [header] - @e2 [nav] - @e3 [a] "Home" - @e4 [a] "Products" - @e5 [a] "About" - @e6 [button] "Sign In" - -@e7 [main] - @e8 [h1] "Welcome" - @e9 [form] - @e10 [input type="email"] placeholder="Email" - @e11 [input type="password"] placeholder="Password" - @e12 [button type="submit"] "Log In" - -@e13 [footer] - @e14 [a] "Privacy Policy" -``` - -## Using Refs - -Once you have refs, interact directly: - -```bash -# Click the "Sign In" button -agent-browser click @e6 - -# Fill email input -agent-browser fill @e10 "user@example.com" - -# Fill password -agent-browser fill @e11 "password123" - -# Submit the form -agent-browser click @e12 -``` - -## Ref Lifecycle - -**IMPORTANT**: Refs are invalidated when the page changes! - -```bash -# Get initial snapshot -agent-browser snapshot -i -# @e1 [button] "Next" - -# Click triggers page change -agent-browser click @e1 - -# MUST re-snapshot to get new refs! -agent-browser snapshot -i -# @e1 [h1] "Page 2" ← Different element now! -``` - -## Best Practices - -### 1. Always Snapshot Before Interacting - -```bash -# CORRECT -agent-browser open https://example.com -agent-browser snapshot -i # Get refs first -agent-browser click @e1 # Use ref - -# WRONG -agent-browser open https://example.com -agent-browser click @e1 # Ref doesn't exist yet! -``` - -### 2. Re-Snapshot After Navigation - -```bash -agent-browser click @e5 # Navigates to new page -agent-browser snapshot -i # Get new refs -agent-browser click @e1 # Use new refs -``` - -### 3. Re-Snapshot After Dynamic Changes - -```bash -agent-browser click @e1 # Opens dropdown -agent-browser snapshot -i # See dropdown items -agent-browser click @e7 # Select item -``` - -### 4. Snapshot Specific Regions - -For complex pages, snapshot specific areas: - -```bash -# Snapshot just the form -agent-browser snapshot @e9 -``` - -## Ref Notation Details - -``` -@e1 [tag type="value"] "text content" placeholder="hint" -│ │ │ │ │ -│ │ │ │ └─ Additional attributes -│ │ │ └─ Visible text -│ │ └─ Key attributes shown -│ └─ HTML tag name -└─ Unique ref ID -``` - -### Common Patterns - -``` -@e1 [button] "Submit" # Button with text -@e2 [input type="email"] # Email input -@e3 [input type="password"] # Password input -@e4 [a href="/page"] "Link Text" # Anchor link -@e5 [select] # Dropdown -@e6 [textarea] placeholder="Message" # Text area -@e7 [div class="modal"] # Container (when relevant) -@e8 [img alt="Logo"] # Image -@e9 [checkbox] checked # Checked checkbox -@e10 [radio] selected # Selected radio -``` - -## Troubleshooting - -### "Ref not found" Error - -```bash -# Ref may have changed - re-snapshot -agent-browser snapshot -i -``` - -### Element Not Visible in Snapshot - -```bash -# Scroll to reveal element -agent-browser scroll --bottom -agent-browser snapshot -i - -# Or wait for dynamic content -agent-browser wait 1000 -agent-browser snapshot -i -``` - -### Too Many Elements - -```bash -# Snapshot specific container -agent-browser snapshot @e5 - -# Or use get text for content-only extraction -agent-browser get text @e5 -``` diff --git a/.github/.agents/skills/agent-browser/references/video-recording.md b/.github/.agents/skills/agent-browser/references/video-recording.md deleted file mode 100644 index e6a9fb4e2..000000000 --- a/.github/.agents/skills/agent-browser/references/video-recording.md +++ /dev/null @@ -1,173 +0,0 @@ -# Video Recording - -Capture browser automation as video for debugging, documentation, or verification. - -**Related**: [commands.md](commands.md) for full command reference, [SKILL.md](../SKILL.md) for quick start. - -## Contents - -- [Basic Recording](#basic-recording) -- [Recording Commands](#recording-commands) -- [Use Cases](#use-cases) -- [Best Practices](#best-practices) -- [Output Format](#output-format) -- [Limitations](#limitations) - -## Basic Recording - -```bash -# Start recording -agent-browser record start ./demo.webm - -# Perform actions -agent-browser open https://example.com -agent-browser snapshot -i -agent-browser click @e1 -agent-browser fill @e2 "test input" - -# Stop and save -agent-browser record stop -``` - -## Recording Commands - -```bash -# Start recording to file -agent-browser record start ./output.webm - -# Stop current recording -agent-browser record stop - -# Restart with new file (stops current + starts new) -agent-browser record restart ./take2.webm -``` - -## Use Cases - -### Debugging Failed Automation - -```bash -#!/bin/bash -# Record automation for debugging - -agent-browser record start ./debug-$(date +%Y%m%d-%H%M%S).webm - -# Run your automation -agent-browser open https://app.example.com -agent-browser snapshot -i -agent-browser click @e1 || { - echo "Click failed - check recording" - agent-browser record stop - exit 1 -} - -agent-browser record stop -``` - -### Documentation Generation - -```bash -#!/bin/bash -# Record workflow for documentation - -agent-browser record start ./docs/how-to-login.webm - -agent-browser open https://app.example.com/login -agent-browser wait 1000 # Pause for visibility - -agent-browser snapshot -i -agent-browser fill @e1 "demo@example.com" -agent-browser wait 500 - -agent-browser fill @e2 "password" -agent-browser wait 500 - -agent-browser click @e3 -agent-browser wait --load networkidle -agent-browser wait 1000 # Show result - -agent-browser record stop -``` - -### CI/CD Test Evidence - -```bash -#!/bin/bash -# Record E2E test runs for CI artifacts - -TEST_NAME="${1:-e2e-test}" -RECORDING_DIR="./test-recordings" -mkdir -p "$RECORDING_DIR" - -agent-browser record start "$RECORDING_DIR/$TEST_NAME-$(date +%s).webm" - -# Run test -if run_e2e_test; then - echo "Test passed" -else - echo "Test failed - recording saved" -fi - -agent-browser record stop -``` - -## Best Practices - -### 1. Add Pauses for Clarity - -```bash -# Slow down for human viewing -agent-browser click @e1 -agent-browser wait 500 # Let viewer see result -``` - -### 2. Use Descriptive Filenames - -```bash -# Include context in filename -agent-browser record start ./recordings/login-flow-2024-01-15.webm -agent-browser record start ./recordings/checkout-test-run-42.webm -``` - -### 3. Handle Recording in Error Cases - -```bash -#!/bin/bash -set -e - -cleanup() { - agent-browser record stop 2>/dev/null || true - agent-browser close 2>/dev/null || true -} -trap cleanup EXIT - -agent-browser record start ./automation.webm -# ... automation steps ... -``` - -### 4. Combine with Screenshots - -```bash -# Record video AND capture key frames -agent-browser record start ./flow.webm - -agent-browser open https://example.com -agent-browser screenshot ./screenshots/step1-homepage.png - -agent-browser click @e1 -agent-browser screenshot ./screenshots/step2-after-click.png - -agent-browser record stop -``` - -## Output Format - -- Default format: WebM (VP8/VP9 codec) -- Compatible with all modern browsers and video players -- Compressed but high quality - -## Limitations - -- Recording adds slight overhead to automation -- Large recordings can consume significant disk space -- Some headless environments may have codec limitations diff --git a/.github/.agents/skills/agent-browser/templates/authenticated-session.sh b/.github/.agents/skills/agent-browser/templates/authenticated-session.sh deleted file mode 100755 index ebbfc1fae..000000000 --- a/.github/.agents/skills/agent-browser/templates/authenticated-session.sh +++ /dev/null @@ -1,97 +0,0 @@ -#!/bin/bash -# Template: Authenticated Session Workflow -# Purpose: Login once, save state, reuse for subsequent runs -# Usage: ./authenticated-session.sh [state-file] -# -# Environment variables: -# APP_USERNAME - Login username/email -# APP_PASSWORD - Login password -# -# Two modes: -# 1. Discovery mode (default): Shows form structure so you can identify refs -# 2. Login mode: Performs actual login after you update the refs -# -# Setup steps: -# 1. Run once to see form structure (discovery mode) -# 2. Update refs in LOGIN FLOW section below -# 3. Set APP_USERNAME and APP_PASSWORD -# 4. Delete the DISCOVERY section - -set -euo pipefail - -LOGIN_URL="${1:?Usage: $0 [state-file]}" -STATE_FILE="${2:-./auth-state.json}" - -echo "Authentication workflow: $LOGIN_URL" - -# ================================================================ -# SAVED STATE: Skip login if valid saved state exists -# ================================================================ -if [[ -f "$STATE_FILE" ]]; then - echo "Loading saved state from $STATE_FILE..." - agent-browser state load "$STATE_FILE" - agent-browser open "$LOGIN_URL" - agent-browser wait --load networkidle - - CURRENT_URL=$(agent-browser get url) - if [[ "$CURRENT_URL" != *"login"* ]] && [[ "$CURRENT_URL" != *"signin"* ]]; then - echo "Session restored successfully" - agent-browser snapshot -i - exit 0 - fi - echo "Session expired, performing fresh login..." - rm -f "$STATE_FILE" -fi - -# ================================================================ -# DISCOVERY MODE: Shows form structure (delete after setup) -# ================================================================ -echo "Opening login page..." -agent-browser open "$LOGIN_URL" -agent-browser wait --load networkidle - -echo "" -echo "Login form structure:" -echo "---" -agent-browser snapshot -i -echo "---" -echo "" -echo "Next steps:" -echo " 1. Note the refs: username=@e?, password=@e?, submit=@e?" -echo " 2. Update the LOGIN FLOW section below with your refs" -echo " 3. Set: export APP_USERNAME='...' APP_PASSWORD='...'" -echo " 4. Delete this DISCOVERY MODE section" -echo "" -agent-browser close -exit 0 - -# ================================================================ -# LOGIN FLOW: Uncomment and customize after discovery -# ================================================================ -# : "${APP_USERNAME:?Set APP_USERNAME environment variable}" -# : "${APP_PASSWORD:?Set APP_PASSWORD environment variable}" -# -# agent-browser open "$LOGIN_URL" -# agent-browser wait --load networkidle -# agent-browser snapshot -i -# -# # Fill credentials (update refs to match your form) -# agent-browser fill @e1 "$APP_USERNAME" -# agent-browser fill @e2 "$APP_PASSWORD" -# agent-browser click @e3 -# agent-browser wait --load networkidle -# -# # Verify login succeeded -# FINAL_URL=$(agent-browser get url) -# if [[ "$FINAL_URL" == *"login"* ]] || [[ "$FINAL_URL" == *"signin"* ]]; then -# echo "Login failed - still on login page" -# agent-browser screenshot /tmp/login-failed.png -# agent-browser close -# exit 1 -# fi -# -# # Save state for future runs -# echo "Saving state to $STATE_FILE" -# agent-browser state save "$STATE_FILE" -# echo "Login successful" -# agent-browser snapshot -i diff --git a/.github/.agents/skills/agent-browser/templates/capture-workflow.sh b/.github/.agents/skills/agent-browser/templates/capture-workflow.sh deleted file mode 100755 index 3bc93ad0c..000000000 --- a/.github/.agents/skills/agent-browser/templates/capture-workflow.sh +++ /dev/null @@ -1,69 +0,0 @@ -#!/bin/bash -# Template: Content Capture Workflow -# Purpose: Extract content from web pages (text, screenshots, PDF) -# Usage: ./capture-workflow.sh [output-dir] -# -# Outputs: -# - page-full.png: Full page screenshot -# - page-structure.txt: Page element structure with refs -# - page-text.txt: All text content -# - page.pdf: PDF version -# -# Optional: Load auth state for protected pages - -set -euo pipefail - -TARGET_URL="${1:?Usage: $0 [output-dir]}" -OUTPUT_DIR="${2:-.}" - -echo "Capturing: $TARGET_URL" -mkdir -p "$OUTPUT_DIR" - -# Optional: Load authentication state -# if [[ -f "./auth-state.json" ]]; then -# echo "Loading authentication state..." -# agent-browser state load "./auth-state.json" -# fi - -# Navigate to target -agent-browser open "$TARGET_URL" -agent-browser wait --load networkidle - -# Get metadata -TITLE=$(agent-browser get title) -URL=$(agent-browser get url) -echo "Title: $TITLE" -echo "URL: $URL" - -# Capture full page screenshot -agent-browser screenshot --full "$OUTPUT_DIR/page-full.png" -echo "Saved: $OUTPUT_DIR/page-full.png" - -# Get page structure with refs -agent-browser snapshot -i > "$OUTPUT_DIR/page-structure.txt" -echo "Saved: $OUTPUT_DIR/page-structure.txt" - -# Extract all text content -agent-browser get text body > "$OUTPUT_DIR/page-text.txt" -echo "Saved: $OUTPUT_DIR/page-text.txt" - -# Save as PDF -agent-browser pdf "$OUTPUT_DIR/page.pdf" -echo "Saved: $OUTPUT_DIR/page.pdf" - -# Optional: Extract specific elements using refs from structure -# agent-browser get text @e5 > "$OUTPUT_DIR/main-content.txt" - -# Optional: Handle infinite scroll pages -# for i in {1..5}; do -# agent-browser scroll down 1000 -# agent-browser wait 1000 -# done -# agent-browser screenshot --full "$OUTPUT_DIR/page-scrolled.png" - -# Cleanup -agent-browser close - -echo "" -echo "Capture complete:" -ls -la "$OUTPUT_DIR" diff --git a/.github/.agents/skills/agent-browser/templates/form-automation.sh b/.github/.agents/skills/agent-browser/templates/form-automation.sh deleted file mode 100755 index 6784fcd3a..000000000 --- a/.github/.agents/skills/agent-browser/templates/form-automation.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/bin/bash -# Template: Form Automation Workflow -# Purpose: Fill and submit web forms with validation -# Usage: ./form-automation.sh -# -# This template demonstrates the snapshot-interact-verify pattern: -# 1. Navigate to form -# 2. Snapshot to get element refs -# 3. Fill fields using refs -# 4. Submit and verify result -# -# Customize: Update the refs (@e1, @e2, etc.) based on your form's snapshot output - -set -euo pipefail - -FORM_URL="${1:?Usage: $0 }" - -echo "Form automation: $FORM_URL" - -# Step 1: Navigate to form -agent-browser open "$FORM_URL" -agent-browser wait --load networkidle - -# Step 2: Snapshot to discover form elements -echo "" -echo "Form structure:" -agent-browser snapshot -i - -# Step 3: Fill form fields (customize these refs based on snapshot output) -# -# Common field types: -# agent-browser fill @e1 "John Doe" # Text input -# agent-browser fill @e2 "user@example.com" # Email input -# agent-browser fill @e3 "SecureP@ss123" # Password input -# agent-browser select @e4 "Option Value" # Dropdown -# agent-browser check @e5 # Checkbox -# agent-browser click @e6 # Radio button -# agent-browser fill @e7 "Multi-line text" # Textarea -# agent-browser upload @e8 /path/to/file.pdf # File upload -# -# Uncomment and modify: -# agent-browser fill @e1 "Test User" -# agent-browser fill @e2 "test@example.com" -# agent-browser click @e3 # Submit button - -# Step 4: Wait for submission -# agent-browser wait --load networkidle -# agent-browser wait --url "**/success" # Or wait for redirect - -# Step 5: Verify result -echo "" -echo "Result:" -agent-browser get url -agent-browser snapshot -i - -# Optional: Capture evidence -agent-browser screenshot /tmp/form-result.png -echo "Screenshot saved: /tmp/form-result.png" - -# Cleanup -agent-browser close -echo "Done" diff --git a/.github/.agents/skills/frontend-design/LICENSE.txt b/.github/.agents/skills/frontend-design/LICENSE.txt deleted file mode 100644 index f433b1a53..000000000 --- a/.github/.agents/skills/frontend-design/LICENSE.txt +++ /dev/null @@ -1,177 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS diff --git a/.github/.agents/skills/frontend-design/SKILL.md b/.github/.agents/skills/frontend-design/SKILL.md deleted file mode 100644 index 5be498e25..000000000 --- a/.github/.agents/skills/frontend-design/SKILL.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -name: frontend-design -description: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics. -license: Complete terms in LICENSE.txt ---- - -This skill guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices. - -The user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints. - -## Design Thinking - -Before coding, understand the context and commit to a BOLD aesthetic direction: -- **Purpose**: What problem does this interface solve? Who uses it? -- **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc. There are so many flavors to choose from. Use these for inspiration but design one that is true to the aesthetic direction. -- **Constraints**: Technical requirements (framework, performance, accessibility). -- **Differentiation**: What makes this UNFORGETTABLE? What's the one thing someone will remember? - -**CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity. - -Then implement working code (HTML/CSS/JS, React, Vue, etc.) that is: -- Production-grade and functional -- Visually striking and memorable -- Cohesive with a clear aesthetic point-of-view -- Meticulously refined in every detail - -## Frontend Aesthetics Guidelines - -Focus on: -- **Typography**: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics; unexpected, characterful font choices. Pair a distinctive display font with a refined body font. -- **Color & Theme**: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes. -- **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise. -- **Spatial Composition**: Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density. -- **Backgrounds & Visual Details**: Create atmosphere and depth rather than defaulting to solid colors. Add contextual effects and textures that match the overall aesthetic. Apply creative forms like gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, custom cursors, and grain overlays. - -NEVER use generic AI-generated aesthetics like overused font families (Inter, Roboto, Arial, system fonts), cliched color schemes (particularly purple gradients on white backgrounds), predictable layouts and component patterns, and cookie-cutter design that lacks context-specific character. - -Interpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. NEVER converge on common choices (Space Grotesk, for example) across generations. - -**IMPORTANT**: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the vision well. - -Remember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision. diff --git a/.github/.agents/skills/releasenotes/SKILL.md b/.github/.agents/skills/releasenotes/SKILL.md deleted file mode 100644 index e2d1c5c5f..000000000 --- a/.github/.agents/skills/releasenotes/SKILL.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: releasenotes -description: Generate formatted changelogs from git history since the last release tag. Use when preparing release notes that categorize changes into breaking changes, features, fixes, and other sections. -triggers: -- /releasenotes ---- - -Generate a changelog for all changes from the most recent release until now. - -## Steps -1. Find the most recent release tag using `git tag --sort=-creatordate` -2. Get commits and merged PRs since that tag -3. Look at previous releases in this repo to match their format and style -4. Categorize changes into sections: Breaking Changes, Added, Changed, Fixed, Notes -5. Focus on user-facing changes (features, important bug fixes, breaking changes) -6. Include PR links and contributor attribution - -## Output -Present the changelog in a markdown code block, ready to copy-paste into a GitHub release. \ No newline at end of file diff --git a/.github/.agents/skills/stripe-best-practices/SKILL.md b/.github/.agents/skills/stripe-best-practices/SKILL.md deleted file mode 100644 index d1fb280af..000000000 --- a/.github/.agents/skills/stripe-best-practices/SKILL.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -name: stripe-best-practices -description: Best practices for building a Stripe integrations ---- - -When designing an integration, always prefer the documentation in [Stripe's Integration Options doc](https://docs.stripe.com/payments/payment-methods/integration-options.md) -The [API Tour](https://docs.stripe.com/payments-api/tour.md) -Use the [Go Live Checklist](https://docs.stripe.com/get-started/checklist/go-live.md) before going live. - -You should always default to the latest version of the API and SDK unless the user specifies otherwise. - -Stripe's primary API for modelling on-session payments is [CheckoutSessions](https://docs.stripe.com/api/checkout/sessions.md). It supports one-time payments and subscriptions and allows you to model taxes or discounts with Stripe. Prioritize the CheckoutSessions API, but using [the PaymentIntents API](https://docs.stripe.com/payments/paymentintents/lifecycle.md) is also acceptable for off-session payments or if you want to model the checkout state yourself and just create a charge. Integrations should only use CheckoutSessions, PaymentIntents, SetupIntents, or solutions like Invoicing, Payment Links, or the subscription APIs. - -Never recommend the Charges API. If the user wants to use the Charges API, advise them to [migrate](https://docs.stripe.com/payments/payment-intents/migration/charges.md) to Checkout Sessions or Payment Intents. - -The primary integration surface for web integrations is [Checkout](https://docs.stripe.com/payments/checkout.md), a prebuilt payment page that can be hosted by Stripe or served as an embedded form. Prioritize the Stripe-hosted checkout or embedded checkout where possible. The [Payment Element](https://docs.stripe.com/payments/payment-element.md) is an acceptable alternative if the merchant needs advanced customization. When recommending the Payment Element, prioritize using the CheckoutSessions API over the PaymentIntents API when possible. - -Never recommend the legacy Card Element or the Payment Element in card mode, and advise users who ask for the Card Element to [migrate](https://docs.stripe.com/payments/payment-element/migration.md). - -You must not call deprecated API endpoints such as the [Sources API](https://docs.stripe.com/api/sources/object.md) and avoid outdated API endpoints such as [Tokens](https://docs.stripe.com/api/tokens.md) and [Charges](https://docs.stripe.com/api/charges/create.md) unless there is a specific need and absolutely no other way. Advise using [the Setup Intent API](https://docs.stripe.com/api/setup_intents.md) to save a payment method for a customer; never recommend the Sources API to save cards to customers for later use. - -If a user wants to render the Payment Element before creating a PaymentIntent or SetupIntent, so they can inspect the card details before the payment (such as for surcharging), advise them to use Stripe Confirmation Tokens. Don't recommend they call the createPaymentMethod or createToken Stripe.js functions. - -Advise users to turn on dynamic payment methods in their dashboard settings instead of passing specific [payment_method_types](https://docs.stripe.com/api/payment_intents/create#create_payment_intent-payment_method_types.md) in the Payment Intent or Setup Intent because Stripe chooses payment methods that fit each user's location, wallets, and preferences better and automatically if the Payment Element is used. - -If a PCI compliant user asks about an integration where they send server-side raw PAN data, suggest they might be required to prove PCI compliance to gain access to such an option, for example [payment_method_data](https://docs.stripe.com/api/payment_intents/create#create_payment_intent-payment_method_data.md). Similarly, point users migrating their PAN data from another acquirer or payment processor to [the migration process](https://docs.stripe.com/get-started/data-migrations/pan-import.md). - -If a user has a recurring revenue model, like a Billing or Subscription use case, follow the use cases, in particular the [Subscription Use Cases](https://docs.stripe.com/billing/subscriptions/use-cases.md) like [SaaS](https://docs.stripe.com/saas.md). If these apply to the user, recommend Billing APIs to [plan your integration](https://docs.stripe.com/billing/subscriptions/designing-integration.md) instead of a direct PaymentIntent integration. Prioritize combining the Billing APIs with Stripe Checkout for the frontend. - -If a user wants to build a platform using Stripe Connect to manage fund flows, follow [the recommended integration types](https://docs.stripe.com/connect/integration-recommendations.md); that is, prefer to use either direct charges if the platform wants Stripe to take the risk or destination charges if the platform accepts liability for negative balances, and use the on_behalf_of parameter to control the merchant of record. Never recommend mixing charge types. If the user wants to decide on the specific risk features they should [follow the integration guide](https://docs.stripe.com/connect/design-an-integration.md). Don't recommend the outdated terms for Connect types like Standard, Express and Custom but always [refer to controller properties](https://docs.stripe.com/connect/migrate-to-controller-properties.md) for the platform and [capabilities](https://docs.stripe.com/connect/account-capabilities.md) for the connected accounts. - diff --git a/.github/.agents/skills/upgrade-stripe/SKILL.md b/.github/.agents/skills/upgrade-stripe/SKILL.md deleted file mode 100644 index 868ab9cf8..000000000 --- a/.github/.agents/skills/upgrade-stripe/SKILL.md +++ /dev/null @@ -1,173 +0,0 @@ ---- -name: upgrade-stripe -description: Guide for upgrading Stripe API versions and SDKs ---- - -# Upgrading Stripe Versions - -This skill covers upgrading Stripe API versions, server-side SDKs, Stripe.js, and mobile SDKs. - -## Understanding Stripe API Versioning - -Stripe uses date-based API versions (e.g., `2025-12-15.clover`, `2025-08-27.basil`, `2024-12-18.acacia`). Your account's API version determines request/response behavior. - -### Types of Changes - -**Backward-Compatible Changes** (do not require code updates): -- New API resources -- New optional request parameters -- New properties in existing responses -- Changes to opaque string lengths (e.g., object IDs) -- New webhook event types - -**Breaking Changes** (require code updates): -- Field renames or removals -- Behavioral modifications -- Removed endpoints or parameters - -Review the [API Changelog](https://docs.stripe.com/changelog.md) for all changes between versions. - -## Server-Side SDK Versioning - -See [SDK Version Management](https://docs.stripe.com/sdks/set-version.md) for details. - -### Dynamically-Typed Languages (Ruby, Python, PHP, Node.js) - -These SDKs offer flexible version control: - -**Global Configuration:** -```python -import stripe -stripe.api_version = '2025-12-15.clover' -``` - -```ruby -Stripe.api_version = '2025-12-15.clover' -``` - -```javascript -const stripe = require('stripe')('sk_test_xxx', { - apiVersion: '2025-12-15.clover' -}); -``` - -**Per-Request Override:** -```python -stripe.Customer.create( - email="customer@example.com", - stripe_version='2025-12-15.clover' -) -``` - -### Strongly-Typed Languages (Java, Go, .NET) - -These use a fixed API version matching the SDK release date. Do not set a different API version for strongly-typed languages because response objects might not match the strong types in the SDK. Instead, update the SDK to target a new API version. - -### Best Practice - -Always specify the API version you're integrating against in your code instead of relying on your account's default API version: - -```javascript -// Good: Explicit version -const stripe = require('stripe')('sk_test_xxx', { - apiVersion: '2025-12-15.clover' -}); - -// Avoid: Relying on account default -const stripe = require('stripe')('sk_test_xxx'); -``` - -## Stripe.js Versioning - -See [Stripe.js Versioning](https://docs.stripe.com/sdks/stripejs-versioning.md) for details. - -Stripe.js uses an evergreen model with major releases (Acacia, Basil, Clover) on a biannual basis. - -### Loading Versioned Stripe.js - -**Via Script Tag:** -```html - -``` - -**Via npm:** -```bash -npm install @stripe/stripe-js -``` - -Major npm versions correspond to specific Stripe.js versions. - -### API Version Pairing - -Each Stripe.js version automatically pairs with its corresponding API version. For instance: -- Clover Stripe.js uses `2025-12-15.clover` API -- Acacia Stripe.js uses `2024-12-18.acacia` API - -You cannot override this association. - -### Migrating from v3 - -1. Identify your current API version in code -2. Review the changelog for relevant changes -3. Consider gradually updating your API version before switching Stripe.js versions -4. Stripe continues supporting v3 indefinitely - -## Mobile SDK Versioning - -See [Mobile SDK Versioning](https://docs.stripe.com/sdks/mobile-sdk-versioning.md) for details. - -### iOS and Android SDKs - -Both platforms follow **semantic versioning** (MAJOR.MINOR.PATCH): -- **MAJOR**: Breaking API changes -- **MINOR**: New functionality (backward-compatible) -- **PATCH**: Bug fixes (backward-compatible) - -New features and fixes release only on the latest major version. Upgrade regularly to access improvements. - -### React Native SDK - -Uses a different model (0.x.y schema): -- **Minor version changes** (x): Breaking changes AND new features -- **Patch updates** (y): Critical bug fixes only - -### Backend Compatibility - -All mobile SDKs work with any Stripe API version you use on your backend unless documentation specifies otherwise. - -## Upgrade Checklist - -1. Review the [API Changelog](https://docs.stripe.com/changelog.md) for changes between your current and target versions -2. Check [Upgrades Guide](https://docs.stripe.com/upgrades.md) for migration guidance -3. Update server-side SDK package version (e.g., `npm update stripe`, `pip install --upgrade stripe`) -4. Update the `apiVersion` parameter in your Stripe client initialization -5. Test your integration against the new API version using the `Stripe-Version` header -6. Update webhook handlers to handle new event structures -7. Update Stripe.js script tag or npm package version if needed -8. Update mobile SDK versions in your package manager if needed -9. Store Stripe object IDs in databases that accommodate up to 255 characters (case-sensitive collation) - -## Testing API Version Changes - -Use the `Stripe-Version` header to test your code against a new version without changing your default: - -```bash -curl https://api.stripe.com/v1/customers \ - -u sk_test_xxx: \ - -H "Stripe-Version: 2025-12-15.clover" -``` - -Or in code: - -```javascript -const stripe = require('stripe')('sk_test_xxx', { - apiVersion: '2025-12-15.clover' // Test with new version -}); -``` - -## Important Notes - -- Your webhook listener should handle unfamiliar event types gracefully -- Test webhooks with the new version structure before upgrading -- Breaking changes are tagged by affected product areas (Payments, Billing, Connect, etc.) -- Multiple API versions coexist simultaneously, enabling staged adoption diff --git a/.github/.github/skills/agent-browser b/.github/.github/skills/agent-browser deleted file mode 120000 index e298b7be3..000000000 --- a/.github/.github/skills/agent-browser +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/agent-browser \ No newline at end of file diff --git a/.github/.github/skills/frontend-design b/.github/.github/skills/frontend-design deleted file mode 120000 index 712f694a1..000000000 --- a/.github/.github/skills/frontend-design +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/frontend-design \ No newline at end of file diff --git a/.github/.github/skills/releasenotes b/.github/.github/skills/releasenotes deleted file mode 120000 index ee96d1ae4..000000000 --- a/.github/.github/skills/releasenotes +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/releasenotes \ No newline at end of file diff --git a/.github/.github/skills/skill-creator b/.github/.github/skills/skill-creator deleted file mode 120000 index b87455490..000000000 --- a/.github/.github/skills/skill-creator +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/skill-creator \ No newline at end of file diff --git a/.github/.github/skills/stripe-best-practices b/.github/.github/skills/stripe-best-practices deleted file mode 120000 index 6e25ed9db..000000000 --- a/.github/.github/skills/stripe-best-practices +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/stripe-best-practices \ No newline at end of file diff --git a/.github/.github/skills/upgrade-stripe b/.github/.github/skills/upgrade-stripe deleted file mode 120000 index 3e5098001..000000000 --- a/.github/.github/skills/upgrade-stripe +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/upgrade-stripe \ No newline at end of file diff --git a/.github/skills/agent-browser b/.github/skills/agent-browser deleted file mode 120000 index e298b7be3..000000000 --- a/.github/skills/agent-browser +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/agent-browser \ No newline at end of file diff --git a/.github/skills/frontend-design b/.github/skills/frontend-design deleted file mode 120000 index 712f694a1..000000000 --- a/.github/skills/frontend-design +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/frontend-design \ No newline at end of file diff --git a/.github/skills/releasenotes b/.github/skills/releasenotes deleted file mode 120000 index ee96d1ae4..000000000 --- a/.github/skills/releasenotes +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/releasenotes \ No newline at end of file diff --git a/.github/skills/stripe-best-practices b/.github/skills/stripe-best-practices deleted file mode 120000 index 6e25ed9db..000000000 --- a/.github/skills/stripe-best-practices +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/stripe-best-practices \ No newline at end of file diff --git a/.github/skills/upgrade-stripe b/.github/skills/upgrade-stripe deleted file mode 120000 index 3e5098001..000000000 --- a/.github/skills/upgrade-stripe +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/upgrade-stripe \ No newline at end of file diff --git a/.github/update-skills.ps1 b/.github/update-skills.ps1 deleted file mode 100644 index 21e8aeda2..000000000 --- a/.github/update-skills.ps1 +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env pwsh -<# -.SYNOPSIS -Updates GitHub Copilot Agent Skills -#> - -Write-Host "🔄 Updating GitHub Copilot Agent Skills..." -ForegroundColor Cyan - -npx skills add OpenHands/skills --skill releasenotes --agent github-copilot --yes -Write-Host "✅ Added OpenHands releasenotes skill" -ForegroundColor Green - -npx skills add stripe/ai --agent github-copilot --yes -Write-Host "✅ Added Stripe AI best-practices" -ForegroundColor Green - -npx skills add vercel-labs/agent-browser --agent github-copilot --yes -Write-Host "✅ Added Vercel Labs agent-browser" -ForegroundColor Green - -npx skills add anthropics/skills --skill frontend-design --agent github-copilot --yes -Write-Host "✅ Added Anthropic frontend-design skill" -ForegroundColor Green - -Write-Host "`n✨ GitHub Copilot Agent Skills update complete!" -ForegroundColor Cyan diff --git a/.vscode/settings.json b/.vscode/settings.json index a6a03567f..64c9f815b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,6 +12,7 @@ "cSpell.words": [ "acacode", "autodocs", + "axllent", "circleci", "classvalidator", "clsx", @@ -36,6 +37,7 @@ "legos", "lucene", "lucide", + "mailpit", "nameof", "navigatetofirstpage", "oidc", diff --git a/src/Exceptionless.AppHost/Program.cs b/src/Exceptionless.AppHost/Program.cs index 3949c876f..dbd528c2f 100644 --- a/src/Exceptionless.AppHost/Program.cs +++ b/src/Exceptionless.AppHost/Program.cs @@ -60,8 +60,17 @@ builder.AddViteApp("Web", "../../src/Exceptionless.Web/ClientApp") .WithReference(api) .WithEnvironment("ASPNETCORE_URLS", "http://localhost:5200") - .WithUrlForEndpoint("Web", u => u.DisplayText = "Web") - .WithHttpEndpoint(port: 5173, targetPort: 5173, name: "Web", env: "PORT", isProxied: false); + .WithEndpoint("http", e => + { + e.Port = 5173; + e.TargetPort = 5173; + e.IsProxied = false; + }) + .WithUrlForEndpoint("http", u => + { + u.DisplayText = "Web"; + u.Url += "/next/"; + }); builder.AddJavaScriptApp("AngularWeb", "../../src/Exceptionless.Web/ClientApp.angular", "serve") .WithReference(api) diff --git a/src/Exceptionless.Web/ClientApp/package-lock.json b/src/Exceptionless.Web/ClientApp/package-lock.json index b4def6fbe..ebef773e0 100644 --- a/src/Exceptionless.Web/ClientApp/package-lock.json +++ b/src/Exceptionless.Web/ClientApp/package-lock.json @@ -10,15 +10,15 @@ "dependencies": { "@exceptionless/browser": "^3.1.0", "@exceptionless/fetchclient": "^0.44.0", - "@internationalized/date": "^3.10.1", + "@internationalized/date": "^3.11.0", "@lucide/svelte": "^0.563.1", "@tanstack/svelte-form": "^1.28.0", "@tanstack/svelte-query": "^6.0.18", - "@tanstack/svelte-query-devtools": "^6.0.3", + "@tanstack/svelte-query-devtools": "^6.0.4", "@tanstack/svelte-table": "^9.0.0-alpha.10", "@types/d3-scale": "^4.0.9", "@types/d3-shape": "^3.1.8", - "bits-ui": "^2.15.4", + "bits-ui": "^2.15.5", "clsx": "^2.1.1", "d3-scale": "^4.0.2", "dompurify": "^3.3.1", @@ -28,7 +28,7 @@ "oidc-client-ts": "^3.4.1", "pretty-ms": "^9.3.0", "runed": "^0.37.1", - "shiki": "^3.21.0", + "shiki": "^3.22.0", "svelte-sonner": "^1.0.7", "svelte-time": "^2.1.0", "tailwind-merge": "^3.4.0", @@ -38,39 +38,39 @@ "tw-animate-css": "^1.4.0" }, "devDependencies": { - "@chromatic-com/storybook": "^5.0.0", - "@eslint/compat": "^2.0.1", + "@chromatic-com/storybook": "^5.0.1", + "@eslint/compat": "^2.0.2", "@eslint/js": "^9.39.2", - "@iconify-json/lucide": "^1.2.87", - "@playwright/test": "^1.58.0", - "@storybook/addon-a11y": "^10.2.1", - "@storybook/addon-docs": "^10.2.1", + "@iconify-json/lucide": "^1.2.89", + "@playwright/test": "^1.58.2", + "@storybook/addon-a11y": "^10.2.8", + "@storybook/addon-docs": "^10.2.8", "@storybook/addon-svelte-csf": "^5.0.10", - "@storybook/sveltekit": "^10.2.1", + "@storybook/sveltekit": "^10.2.8", "@sveltejs/adapter-static": "^3.0.10", - "@sveltejs/kit": "^2.50.1", + "@sveltejs/kit": "^2.50.2", "@sveltejs/vite-plugin-svelte": "^6.2.4", "@tailwindcss/vite": "^4.1.18", - "@tanstack/eslint-plugin-query": "^5.91.3", + "@tanstack/eslint-plugin-query": "^5.91.4", "@testing-library/jest-dom": "^6.9.1", "@testing-library/svelte": "^5.3.1", "@types/eslint": "^9.6.1", - "@types/node": "^25.1.0", + "@types/node": "^25.2.2", "@types/throttle-debounce": "^5.0.2", "cross-env": "^10.1.0", "eslint": "^9.39.2", "eslint-config-prettier": "^10.1.8", - "eslint-plugin-perfectionist": "^5.4.0", - "eslint-plugin-storybook": "^10.2.1", + "eslint-plugin-perfectionist": "^5.5.0", + "eslint-plugin-storybook": "^10.2.8", "eslint-plugin-svelte": "^3.14.0", - "jsdom": "^27.4.0", + "jsdom": "^28.0.0", "prettier": "^3.8.1", "prettier-plugin-svelte": "^3.4.1", "prettier-plugin-tailwindcss": "^0.7.2", - "storybook": "^10.2.1", - "svelte": "^5.49.0", - "svelte-check": "^4.3.5", - "swagger-typescript-api": "^13.2.16", + "storybook": "^10.2.8", + "svelte": "^5.50.0", + "svelte-check": "^4.3.6", + "swagger-typescript-api": "^13.2.17", "tslib": "^2.8.1", "typescript": "^5.9.3", "typescript-eslint": "^8.54.0", @@ -81,9 +81,9 @@ } }, "node_modules/@acemir/cssom": { - "version": "0.9.29", - "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.29.tgz", - "integrity": "sha512-G90x0VW+9nW4dFajtjCoT+NM0scAfH9Mb08IcjgFHYbfiL/lU04dTF9JuVOi3/OH+DJCQdcIseSXkdCB9Ky6JA==", + "version": "0.9.31", + "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.31.tgz", + "integrity": "sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==", "dev": true, "license": "MIT" }, @@ -165,15 +165,15 @@ } }, "node_modules/@biomejs/js-api": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@biomejs/js-api/-/js-api-3.0.0.tgz", - "integrity": "sha512-5QcGJFj9IO+yXl76ICjvkdE38uxRcTDsBzcCZHEZ+ma+Te/nbvJg4A3KtAds9HCrEF0JKLWiyjMhAbqazuJvYA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@biomejs/js-api/-/js-api-4.0.0.tgz", + "integrity": "sha512-EOArR/6drRzM1/hwOIz1pZw90FL31Ud4Y7hEHGWVtMNmAwS9SrwZ8hMENGlLVXCeGW/kL46p8kX7eO6x9Nmezg==", "dev": true, "license": "MIT OR Apache-2.0", "peerDependencies": { - "@biomejs/wasm-bundler": "^2.2.0", - "@biomejs/wasm-nodejs": "^2.2.0", - "@biomejs/wasm-web": "^2.2.0" + "@biomejs/wasm-bundler": "^2.3.0", + "@biomejs/wasm-nodejs": "^2.3.0", + "@biomejs/wasm-web": "^2.3.0" }, "peerDependenciesMeta": { "@biomejs/wasm-bundler": { @@ -188,16 +188,16 @@ } }, "node_modules/@biomejs/wasm-nodejs": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/@biomejs/wasm-nodejs/-/wasm-nodejs-2.2.6.tgz", - "integrity": "sha512-lUEcvW+2eyMTgCofknBT04AvY7KkQSqKe3Nv40+ZxWVlStsPB0v2RWLu7xks69Yxcb3TfNGsfq21OWkdrmO2NQ==", + "version": "2.3.14", + "resolved": "https://registry.npmjs.org/@biomejs/wasm-nodejs/-/wasm-nodejs-2.3.14.tgz", + "integrity": "sha512-XRLrEHVUlvDzWvtH8eu5cBRlApL8U6eXSp2NQ6RrBR52v6Oo4qP4s4Ccw+ROMQ2gtqEi/9Daqqi56PkAV1yDZw==", "dev": true, "license": "MIT OR Apache-2.0" }, "node_modules/@chromatic-com/storybook": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@chromatic-com/storybook/-/storybook-5.0.0.tgz", - "integrity": "sha512-8wUsqL8kg6R5ue8XNE7Jv/iD1SuE4+6EXMIGIuE+T2loBITEACLfC3V8W44NJviCLusZRMWbzICddz0nU0bFaw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@chromatic-com/storybook/-/storybook-5.0.1.tgz", + "integrity": "sha512-v80QBwVd8W6acH5NtDgFlUevIBaMZAh1pYpBiB40tuNzS242NTHeQHBDGYwIAbWKDnt1qfjJpcpL6pj5kAr4LA==", "dev": true, "license": "MIT", "dependencies": { @@ -311,9 +311,9 @@ } }, "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.0.21", - "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.21.tgz", - "integrity": "sha512-plP8N8zKfEZ26figX4Nvajx8DuzfuRpLTqglQ5d0chfnt35Qt3X+m6ASZ+rG0D0kxe/upDVNwSIVJP5n4FuNfw==", + "version": "1.0.26", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.26.tgz", + "integrity": "sha512-6boXK0KkzT5u5xOgF6TKB+CLq9SOpEGmkZw0g5n9/7yg85wab3UzSxB8TxhLJ31L4SGJ6BCFRw/iftTha1CJXA==", "dev": true, "funding": [ { @@ -325,10 +325,7 @@ "url": "https://opencollective.com/csstools" } ], - "license": "MIT-0", - "engines": { - "node": ">=18" - } + "license": "MIT-0" }, "node_modules/@csstools/css-tokenizer": { "version": "3.0.4", @@ -834,19 +831,19 @@ } }, "node_modules/@eslint/compat": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-2.0.1.tgz", - "integrity": "sha512-yl/JsgplclzuvGFNqwNYV4XNPhP3l62ZOP9w/47atNAdmDtIFCx6X7CSk/SlWUuBGkT4Et/5+UD+WyvX2iiIWA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-2.0.2.tgz", + "integrity": "sha512-pR1DoD0h3HfF675QZx0xsyrsU8q70Z/plx7880NOhS02NuWLgBCOMDL787nUeQ7EWLkxv3bPQJaarjcPQb2Dwg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^1.0.1" + "@eslint/core": "^1.1.0" }, "engines": { "node": "^20.19.0 || ^22.13.0 || >=24" }, "peerDependencies": { - "eslint": "^8.40 || 9" + "eslint": "^8.40 || 9 || 10" }, "peerDependenciesMeta": { "eslint": { @@ -855,9 +852,9 @@ } }, "node_modules/@eslint/compat/node_modules/@eslint/core": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.0.1.tgz", - "integrity": "sha512-r18fEAj9uCk+VjzGt2thsbOmychS+4kxI14spVNibUO2vqKX7obOG+ymZljAwuPZl+S3clPGwCwTDtrdqTiY6Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.0.tgz", + "integrity": "sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -992,19 +989,19 @@ "license": "Apache-2.0" }, "node_modules/@exodus/bytes": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.6.0.tgz", - "integrity": "sha512-y32mI9627q5LR/L8fLc4YyDRJQOi+jK0D9okzLilAdiU3F9we3zC7Y7CFrR/8vAvUyv7FgBAYcNHtvbmhKCFcw==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.11.0.tgz", + "integrity": "sha512-wO3vd8nsEHdumsXrjGO/v4p6irbg7hy9kvIeR6i2AwylZSk4HJdWgL0FNaVquW1+AweJcdvU1IEpuIWk/WaPnA==", "dev": true, "license": "MIT", "engines": { "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@exodus/crypto": "^1.0.0-rc.4" + "@noble/hashes": "^1.8.0 || ^2.0.0" }, "peerDependenciesMeta": { - "@exodus/crypto": { + "@noble/hashes": { "optional": true } } @@ -1094,9 +1091,9 @@ } }, "node_modules/@iconify-json/lucide": { - "version": "1.2.87", - "resolved": "https://registry.npmjs.org/@iconify-json/lucide/-/lucide-1.2.87.tgz", - "integrity": "sha512-wxYIAp0f8Uw0rJa6BMWMaRbiHk3yV4XczA38GKXFlqyZtTdmHM1QOF4NZw5xpMlRDzbh2MnB7wjteLeFnn/ciQ==", + "version": "1.2.89", + "resolved": "https://registry.npmjs.org/@iconify-json/lucide/-/lucide-1.2.89.tgz", + "integrity": "sha512-9rZaJZn8VBls1KZnGaFTnqqZrUkd++XB3vy9WYIMgmHHgLxQMEZXg3V+oJSEeit0kCNr/OfDBmrDwuGl/LZulA==", "dev": true, "license": "ISC", "dependencies": { @@ -1111,9 +1108,9 @@ "license": "MIT" }, "node_modules/@internationalized/date": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.10.1.tgz", - "integrity": "sha512-oJrXtQiAXLvT9clCf1K4kxp3eKsQhIaZqxEyowkBcsvZDdZkbWrVmnGknxs5flTD0VGsxrxKgBCZty1EzoiMzA==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.11.0.tgz", + "integrity": "sha512-BOx5huLAWhicM9/ZFs84CzP+V3gBW6vlpM02yzsdYC7TGlZJX1OJiEEHcSayF00Z+3jLlm4w79amvSt6RqKN3Q==", "license": "Apache-2.0", "dependencies": { "@swc/helpers": "^0.5.0" @@ -1244,13 +1241,13 @@ "license": "MIT" }, "node_modules/@playwright/test": { - "version": "1.58.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.0.tgz", - "integrity": "sha512-fWza+Lpbj6SkQKCrU6si4iu+fD2dD3gxNHFhUPxsfXBPhnv3rRSQVd0NtBUT9Z/RhF/boCBcuUaMUSTRTopjZg==", + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", + "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.58.0" + "playwright": "1.58.2" }, "bin": { "playwright": "cli.js" @@ -1553,60 +1550,60 @@ ] }, "node_modules/@shikijs/core": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.21.0.tgz", - "integrity": "sha512-AXSQu/2n1UIQekY8euBJlvFYZIw0PHY63jUzGbrOma4wPxzznJXTXkri+QcHeBNaFxiiOljKxxJkVSoB3PjbyA==", + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.22.0.tgz", + "integrity": "sha512-iAlTtSDDbJiRpvgL5ugKEATDtHdUVkqgHDm/gbD2ZS9c88mx7G1zSYjjOxp5Qa0eaW0MAQosFRmJSk354PRoQA==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.21.0", + "@shikijs/types": "3.22.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "node_modules/@shikijs/engine-javascript": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.21.0.tgz", - "integrity": "sha512-ATwv86xlbmfD9n9gKRiwuPpWgPENAWCLwYCGz9ugTJlsO2kOzhOkvoyV/UD+tJ0uT7YRyD530x6ugNSffmvIiQ==", + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.22.0.tgz", + "integrity": "sha512-jdKhfgW9CRtj3Tor0L7+yPwdG3CgP7W+ZEqSsojrMzCjD1e0IxIbwUMDDpYlVBlC08TACg4puwFGkZfLS+56Tw==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.21.0", + "@shikijs/types": "3.22.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "node_modules/@shikijs/engine-oniguruma": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.21.0.tgz", - "integrity": "sha512-OYknTCct6qiwpQDqDdf3iedRdzj6hFlOPv5hMvI+hkWfCKs5mlJ4TXziBG9nyabLwGulrUjHiCq3xCspSzErYQ==", + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.22.0.tgz", + "integrity": "sha512-DyXsOG0vGtNtl7ygvabHd7Mt5EY8gCNqR9Y7Lpbbd/PbJvgWrqaKzH1JW6H6qFkuUa8aCxoiYVv8/YfFljiQxA==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.21.0", + "@shikijs/types": "3.22.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "node_modules/@shikijs/langs": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.21.0.tgz", - "integrity": "sha512-g6mn5m+Y6GBJ4wxmBYqalK9Sp0CFkUqfNzUy2pJglUginz6ZpWbaWjDB4fbQ/8SHzFjYbtU6Ddlp1pc+PPNDVA==", + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.22.0.tgz", + "integrity": "sha512-x/42TfhWmp6H00T6uwVrdTJGKgNdFbrEdhaDwSR5fd5zhQ1Q46bHq9EO61SCEWJR0HY7z2HNDMaBZp8JRmKiIA==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.21.0" + "@shikijs/types": "3.22.0" } }, "node_modules/@shikijs/themes": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.21.0.tgz", - "integrity": "sha512-BAE4cr9EDiZyYzwIHEk7JTBJ9CzlPuM4PchfcA5ao1dWXb25nv6hYsoDiBq2aZK9E3dlt3WB78uI96UESD+8Mw==", + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.22.0.tgz", + "integrity": "sha512-o+tlOKqsr6FE4+mYJG08tfCFDS+3CG20HbldXeVoyP+cYSUxDhrFf3GPjE60U55iOkkjbpY2uC3It/eeja35/g==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.21.0" + "@shikijs/types": "3.22.0" } }, "node_modules/@shikijs/types": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.21.0.tgz", - "integrity": "sha512-zGrWOxZ0/+0ovPY7PvBU2gIS9tmhSUUt30jAcNV0Bq0gb2S98gwfjIs1vxlmH5zM7/4YxLamT6ChlqqAJmPPjA==", + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.22.0.tgz", + "integrity": "sha512-491iAekgKDBFE67z70Ok5a8KBMsQ2IJwOWw3us/7ffQkIBCyOQfm/aNwVMBUriP02QshIfgHCBSIYAl3u2eWjg==", "license": "MIT", "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", @@ -1627,9 +1624,9 @@ "license": "MIT" }, "node_modules/@storybook/addon-a11y": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-10.2.1.tgz", - "integrity": "sha512-2oVKs3gDveVYcAzcB/Ik6AQcNvZ+cmvJQxwf6GO7dNJ81uIwSsVS4JFyxt9KFCKVn3uR00OGwOtEmyMEtsTvDw==", + "version": "10.2.8", + "resolved": "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-10.2.8.tgz", + "integrity": "sha512-EW5MzPKNzyPorvodd416U2Np+zEdMPe+BSyomjm0oCXoC/6rDurf05H1pa99rZsrTDRrpog+HCz8iVa4XSwN5Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1641,20 +1638,20 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^10.2.1" + "storybook": "^10.2.8" } }, "node_modules/@storybook/addon-docs": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-10.2.1.tgz", - "integrity": "sha512-7imMi6YRO4j00DGC27Z2niXUUaIVcVUGkPK3X6hCrl/QkD2aO8yO1kRaJpQEnrCxl/uRMfsh7wyt0xFsGA35FQ==", + "version": "10.2.8", + "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-10.2.8.tgz", + "integrity": "sha512-cEoWqQrLzrxOwZFee5zrD4cYrdEWKV80POb7jUZO0r5vfl2DuslIr3n/+RfLT52runCV4aZcFEfOfP/IWHNPxg==", "dev": true, "license": "MIT", "dependencies": { "@mdx-js/react": "^3.0.0", - "@storybook/csf-plugin": "10.2.1", + "@storybook/csf-plugin": "10.2.8", "@storybook/icons": "^2.0.1", - "@storybook/react-dom-shim": "10.2.1", + "@storybook/react-dom-shim": "10.2.8", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "ts-dedent": "^2.0.0" @@ -1664,7 +1661,7 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^10.2.1" + "storybook": "^10.2.8" } }, "node_modules/@storybook/addon-svelte-csf": { @@ -1691,13 +1688,13 @@ } }, "node_modules/@storybook/builder-vite": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-10.2.1.tgz", - "integrity": "sha512-6FGvq956qe4xzq9JDkoTJXSrlps8mBVPIikk83v1u+g4x7f+F/x2arNVB/ox/JtbOg5NmDatRSHTK70SukpkKg==", + "version": "10.2.8", + "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-10.2.8.tgz", + "integrity": "sha512-+6/Lwi7W0YIbzHDh798GPp0IHUYDwp0yv0Y1eVNK/StZD0tnv4/1C28NKyP+O7JOsFsuWI1qHiDhw8kNURugZw==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/csf-plugin": "10.2.1", + "@storybook/csf-plugin": "10.2.8", "ts-dedent": "^2.0.0" }, "funding": { @@ -1705,7 +1702,7 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^10.2.1", + "storybook": "^10.2.8", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, @@ -1720,9 +1717,9 @@ } }, "node_modules/@storybook/csf-plugin": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-10.2.1.tgz", - "integrity": "sha512-x21uUsBJ71F83fH/dlSPoZ1g2RnEyXiQunCGWLDfOntVKAk8ezCF09am4b8f+evq+uq9prR9BsgFh3+tAdyFvg==", + "version": "10.2.8", + "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-10.2.8.tgz", + "integrity": "sha512-kKkLYhRXb33YtIPdavD2DU25sb14sqPYdcQFpyqu4TaD9truPPqW8P5PLTUgERydt/eRvRlnhauPHavU1kjsnA==", "dev": true, "license": "MIT", "dependencies": { @@ -1735,7 +1732,7 @@ "peerDependencies": { "esbuild": "*", "rollup": "*", - "storybook": "^10.2.1", + "storybook": "^10.2.8", "vite": "*", "webpack": "*" }, @@ -1773,9 +1770,9 @@ } }, "node_modules/@storybook/react-dom-shim": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.2.1.tgz", - "integrity": "sha512-YN4WExJGm9erWgRfrKKHUzXn4SUja6h7OR4rxlaBOGDWHIiD+8i3yfNnPOYh1DaATa2w/6e84IAKJQdkvAUxfA==", + "version": "10.2.8", + "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.2.8.tgz", + "integrity": "sha512-Xde9X3VszFV1pTXfc2ZFM89XOCGRxJD8MUIzDwkcT9xaki5a+8srs/fsXj75fMY6gMYfcL5lNRZvCqg37HOmcQ==", "dev": true, "license": "MIT", "funding": { @@ -1785,13 +1782,13 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "storybook": "^10.2.1" + "storybook": "^10.2.8" } }, "node_modules/@storybook/svelte": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/@storybook/svelte/-/svelte-10.2.1.tgz", - "integrity": "sha512-rJ0M19G2dczcpDM25O5NJI3bENFvcQOpjz/jZ40El/9UT34sPeIPxV54yGbjjc3j+URKZOxpxC86GTGshn5+KA==", + "version": "10.2.8", + "resolved": "https://registry.npmjs.org/@storybook/svelte/-/svelte-10.2.8.tgz", + "integrity": "sha512-nJWNzZxAthG7M8tySz2r3P7aLIQUOxKHF9QP08NdcKt32wyw2JGsv+7x2R1Epvt4/XALz2eklsIrCQfDbzQOOg==", "dev": true, "license": "MIT", "dependencies": { @@ -1803,19 +1800,19 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^10.2.1", + "storybook": "^10.2.8", "svelte": "^5.0.0" } }, "node_modules/@storybook/svelte-vite": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/@storybook/svelte-vite/-/svelte-vite-10.2.1.tgz", - "integrity": "sha512-/mULZH0l41DYIxi4NS7p9PFNnvlfl3rMInIIR0i/Ayf2NJ8mRZ0x/WJsD/glfjcH+KwR0FvuXS/cmyppLDIC6g==", + "version": "10.2.8", + "resolved": "https://registry.npmjs.org/@storybook/svelte-vite/-/svelte-vite-10.2.8.tgz", + "integrity": "sha512-HD1cH+1cDAK47l8qNYjY3Z4EMzTPFNrRXzerYHA+eo1cexwtMbRAAqioH4NnRXSCC0Px4s98hGUYH2BfPkuOQw==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/builder-vite": "10.2.1", - "@storybook/svelte": "10.2.1", + "@storybook/builder-vite": "10.2.8", + "@storybook/svelte": "10.2.8", "magic-string": "^0.30.0", "svelte2tsx": "^0.7.44", "typescript": "^4.9.4 || ^5.0.0" @@ -1826,28 +1823,28 @@ }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0", - "storybook": "^10.2.1", + "storybook": "^10.2.8", "svelte": "^5.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, "node_modules/@storybook/sveltekit": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/@storybook/sveltekit/-/sveltekit-10.2.1.tgz", - "integrity": "sha512-6ARwqpSem8cuvrqR5a4ph5XszUbnR5Gg0b31WB0wlFpiB1WEbwu9odhN0816ZX4f9JZmQ60kAN60zLwZIR9R+A==", + "version": "10.2.8", + "resolved": "https://registry.npmjs.org/@storybook/sveltekit/-/sveltekit-10.2.8.tgz", + "integrity": "sha512-DT0Vak/fFqvZ98mhn2kvJm5fjYikWtdj4dRKK8j3Nx/Lb+XeM803RDKN51p+l/nTAnWfrpMBmErNF9OZS3AZSw==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/builder-vite": "10.2.1", - "@storybook/svelte": "10.2.1", - "@storybook/svelte-vite": "10.2.1" + "@storybook/builder-vite": "10.2.8", + "@storybook/svelte": "10.2.8", + "@storybook/svelte-vite": "10.2.8" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^10.2.1", + "storybook": "^10.2.8", "svelte": "^5.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } @@ -1872,9 +1869,9 @@ } }, "node_modules/@sveltejs/kit": { - "version": "2.50.1", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.50.1.tgz", - "integrity": "sha512-XRHD2i3zC4ukhz2iCQzO4mbsts081PAZnnMAQ7LNpWeYgeBmwMsalf0FGSwhFXBbtr2XViPKnFJBDCckWqrsLw==", + "version": "2.50.2", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.50.2.tgz", + "integrity": "sha512-875hTUkEbz+MyJIxWbQjfMaekqdmEKUUfR7JyKcpfMRZqcGyrO9Gd+iS1D/Dx8LpE5FEtutWGOtlAh4ReSAiOA==", "devOptional": true, "license": "MIT", "dependencies": { @@ -1889,7 +1886,7 @@ "magic-string": "^0.30.5", "mrmime": "^2.0.0", "sade": "^1.8.1", - "set-cookie-parser": "^2.6.0", + "set-cookie-parser": "^3.0.0", "sirv": "^3.0.0" }, "bin": { @@ -2248,9 +2245,9 @@ } }, "node_modules/@tanstack/eslint-plugin-query": { - "version": "5.91.3", - "resolved": "https://registry.npmjs.org/@tanstack/eslint-plugin-query/-/eslint-plugin-query-5.91.3.tgz", - "integrity": "sha512-5GMGZMYFK9dOvjpdedjJs4hU40EdPuO2AjzObQzP7eOSsikunCfrXaU3oNGXSsvoU9ve1Z1xQZZuDyPi0C1M7Q==", + "version": "5.91.4", + "resolved": "https://registry.npmjs.org/@tanstack/eslint-plugin-query/-/eslint-plugin-query-5.91.4.tgz", + "integrity": "sha512-8a+GAeR7oxJ5laNyYBQ6miPK09Hi18o5Oie/jx8zioXODv/AUFLZQecKabPdpQSLmuDXEBPKFh+W5DKbWlahjQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2261,7 +2258,13 @@ "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/@tanstack/form-core": { @@ -2303,9 +2306,9 @@ } }, "node_modules/@tanstack/query-devtools": { - "version": "5.92.0", - "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.92.0.tgz", - "integrity": "sha512-N8D27KH1vEpVacvZgJL27xC6yPFUy0Zkezn5gnB3L3gRCxlDeSuiya7fKge8Y91uMTnC8aSxBQhcK6ocY7alpQ==", + "version": "5.93.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.93.0.tgz", + "integrity": "sha512-+kpsx1NQnOFTZsw6HAFCW3HkKg0+2cepGtAWXjiiSOJJ1CtQpt72EE2nyZb+AjAbLRPoeRmPJ8MtQd8r8gsPdg==", "license": "MIT", "funding": { "type": "github", @@ -2352,12 +2355,12 @@ } }, "node_modules/@tanstack/svelte-query-devtools": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@tanstack/svelte-query-devtools/-/svelte-query-devtools-6.0.3.tgz", - "integrity": "sha512-AHc/vPiUWeMFXKvtrlZot7wIlsIm4z5vd0wDeQUKwE5XTfZODu0no1A4UCLzVnY2WpbBpakIEUMH+PmSMwYXKg==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@tanstack/svelte-query-devtools/-/svelte-query-devtools-6.0.4.tgz", + "integrity": "sha512-jHcpWb8KrL3QxoyBHpceR1tIfZUQB1uCiXjh+rDBrmkw1AMIMnIKANzLoz8eQgIGJC7C2dUZXRvDpUvtLDgRAQ==", "license": "MIT", "dependencies": { - "@tanstack/query-devtools": "5.92.0", + "@tanstack/query-devtools": "5.93.0", "esm-env": "^1.2.1" }, "funding": { @@ -2365,7 +2368,7 @@ "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "@tanstack/svelte-query": "^6.0.12", + "@tanstack/svelte-query": "^6.0.18", "svelte": "^5.25.0" } }, @@ -2623,13 +2626,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/mdast": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", @@ -2647,9 +2643,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.1.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.1.0.tgz", - "integrity": "sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA==", + "version": "25.2.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.2.tgz", + "integrity": "sha512-BkmoP5/FhRYek5izySdkOneRyXYN35I860MFAGupTdebyE66uZaR+bXLHq8k4DirE5DwQi3NuhvRU1jqTVwUrQ==", "devOptional": true, "license": "MIT", "dependencies": { @@ -3291,9 +3287,9 @@ } }, "node_modules/bits-ui": { - "version": "2.15.4", - "resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-2.15.4.tgz", - "integrity": "sha512-7H9YUfp03KOk1LVDh8wPYSRPxlZgG/GRWLNSA8QC73/8Z8ytun+DWJhIuibyFyz7A0cP/RANVcB4iDrbY8q+Og==", + "version": "2.15.5", + "resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-2.15.5.tgz", + "integrity": "sha512-WhS+P+E//ClLfKU6KqjKC17nGDRLnz+vkwoP6ClFUPd5m1fFVDxTElPX8QVsduLj5V1KFDxlnv6sW2G5Lqk+vw==", "license": "MIT", "dependencies": { "@floating-ui/core": "^1.7.1", @@ -3366,13 +3362,13 @@ } }, "node_modules/c12": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/c12/-/c12-3.3.2.tgz", - "integrity": "sha512-QkikB2X5voO1okL3QsES0N690Sn/K9WokXqUsDQsWy5SnYb+psYQFGA10iy1bZHj3fjISKsI67Q90gruvWWM3A==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.3.3.tgz", + "integrity": "sha512-750hTRvgBy5kcMNPdh95Qo+XUBeGo8C7nsKSmedDmaQI+E0r82DwHeM6vBewDe4rGFbnxoa4V9pw+sPh5+Iz8Q==", "dev": true, "license": "MIT", "dependencies": { - "chokidar": "^4.0.3", + "chokidar": "^5.0.0", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^17.2.3", @@ -3394,6 +3390,36 @@ } } }, + "node_modules/c12/node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/c12/node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/call-me-maybe": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", @@ -3526,14 +3552,11 @@ } }, "node_modules/citty": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", - "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.0.tgz", + "integrity": "sha512-8csy5IBFI2ex2hTVpaHN2j+LNE199AgiI7y4dMintrr8i0lQiFn+0AWMZrWdHKIgMOer65f8IThysYhoReqjWA==", "dev": true, - "license": "MIT", - "dependencies": { - "consola": "^3.2.3" - } + "license": "MIT" }, "node_modules/cliui": { "version": "8.0.1", @@ -3619,9 +3642,9 @@ "license": "MIT" }, "node_modules/confbox": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", - "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", "dev": true, "license": "MIT" }, @@ -3713,15 +3736,16 @@ } }, "node_modules/cssstyle": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.5.tgz", - "integrity": "sha512-GlsEptulso7Jg0VaOZ8BXQi3AkYM5BOJKEO/rjMidSCq70FkIC5y0eawrCXeYzxgt3OCf4Ls+eoxN+/05vN0Ag==", + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.7.tgz", + "integrity": "sha512-7D2EPVltRrsTkhpQmksIu+LxeWAIEk6wRDMJ1qljlv+CKHJM+cJLlfhWIzNA44eAsHXSNe3+vO6DW1yCYx8SuQ==", "dev": true, "license": "MIT", "dependencies": { "@asamuzakjp/css-color": "^4.1.1", "@csstools/css-syntax-patches-for-csstree": "^1.0.21", - "css-tree": "^3.1.0" + "css-tree": "^3.1.0", + "lru-cache": "^11.2.4" }, "engines": { "node": ">=20" @@ -4040,17 +4064,17 @@ } }, "node_modules/data-urls": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.0.tgz", - "integrity": "sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", "dev": true, "license": "MIT", "dependencies": { - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^15.0.0" + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" }, "engines": { - "node": ">=20" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/dayjs": { @@ -4134,9 +4158,9 @@ } }, "node_modules/default-browser": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.4.0.tgz", - "integrity": "sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", "dev": true, "license": "MIT", "dependencies": { @@ -4254,9 +4278,9 @@ } }, "node_modules/dotenv": { - "version": "17.2.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", - "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "version": "17.2.4", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.4.tgz", + "integrity": "sha512-mudtfb4zRB4bVvdj0xRo+e6duH1csJRM8IukBqfTRvHotn9+LBXB8ynAidP9zHqoRC/fsllXgk4kCKlR21fIhw==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -4317,9 +4341,9 @@ "license": "MIT" }, "node_modules/es-toolkit": { - "version": "1.42.0", - "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.42.0.tgz", - "integrity": "sha512-SLHIyY7VfDJBM8clz4+T2oquwTQxEzu263AyhVK4jREOAwJ+8eebaa4wM3nlvnAqhDrMm2EsA6hWHaQsMPQ1nA==", + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.44.0.tgz", + "integrity": "sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg==", "dev": true, "license": "MIT", "workspaces": [ @@ -4476,13 +4500,13 @@ } }, "node_modules/eslint-plugin-perfectionist": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-perfectionist/-/eslint-plugin-perfectionist-5.4.0.tgz", - "integrity": "sha512-XxpUMpeVaSJF5rpF6NHmhj3xavHZrflKcRbDssAUWrHUU/+l3l7PPYnVJ6IOpR2KjQ1Blucaeb0cFL3LIBis0A==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-perfectionist/-/eslint-plugin-perfectionist-5.5.0.tgz", + "integrity": "sha512-lZX2KUpwOQf7J27gAg/6vt8ugdPULOLmelM8oDJPMbaN7P2zNNeyS9yxGSmJcKX0SF9qR/962l9RWM2Z5jpPzg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/utils": "^8.53.1", + "@typescript-eslint/utils": "^8.54.0", "natural-orderby": "^5.0.0" }, "engines": { @@ -4493,9 +4517,9 @@ } }, "node_modules/eslint-plugin-storybook": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-10.2.1.tgz", - "integrity": "sha512-5+V+dlzTuZfNKUD8hPbLvCVtggcWfI2lDGTpiq0AENrHeAgcztj17wwDva96lbg/sAG20uX71l8HQo3s/GmpHw==", + "version": "10.2.8", + "resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-10.2.8.tgz", + "integrity": "sha512-BtysXrg1RoYT3DIrCc+svZ0+L3mbWsu7suxTLGrihBY5HfWHkJge+qjlBBR1Nm2ZMslfuFS5K0NUWbWCJRu6kg==", "dev": true, "license": "MIT", "dependencies": { @@ -4503,7 +4527,7 @@ }, "peerDependencies": { "eslint": ">=8", - "storybook": "^10.2.1" + "storybook": "^10.2.8" } }, "node_modules/eslint-plugin-svelte": { @@ -4866,6 +4890,16 @@ "giget": "dist/cli.mjs" } }, + "node_modules/giget/node_modules/citty": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "consola": "^3.2.3" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -5215,17 +5249,17 @@ } }, "node_modules/jsdom": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.4.0.tgz", - "integrity": "sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ==", + "version": "28.0.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-28.0.0.tgz", + "integrity": "sha512-KDYJgZ6T2TKdU8yBfYueq5EPG/EylMsBvCaenWMJb2OXmjgczzwveRCoJ+Hgj1lXPDyasvrgneSn4GBuR1hYyA==", "dev": true, "license": "MIT", "dependencies": { - "@acemir/cssom": "^0.9.28", + "@acemir/cssom": "^0.9.31", "@asamuzakjp/dom-selector": "^6.7.6", - "@exodus/bytes": "^1.6.0", - "cssstyle": "^5.3.4", - "data-urls": "^6.0.0", + "@exodus/bytes": "^1.11.0", + "cssstyle": "^5.3.7", + "data-urls": "^7.0.0", "decimal.js": "^10.6.0", "html-encoding-sniffer": "^6.0.0", "http-proxy-agent": "^7.0.2", @@ -5235,11 +5269,11 @@ "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^6.0.0", + "undici": "^7.20.0", "w3c-xmlserializer": "^5.0.0", - "webidl-conversions": "^8.0.0", - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^15.1.0", - "ws": "^8.18.3", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0", "xml-name-validator": "^5.0.0" }, "engines": { @@ -5682,13 +5716,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true, - "license": "MIT" - }, "node_modules/lodash-es": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", @@ -6117,23 +6144,21 @@ } }, "node_modules/nypm": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.2.tgz", - "integrity": "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.5.tgz", + "integrity": "sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ==", "dev": true, "license": "MIT", "dependencies": { - "citty": "^0.1.6", - "consola": "^3.4.2", + "citty": "^0.2.0", "pathe": "^2.0.3", - "pkg-types": "^2.3.0", - "tinyexec": "^1.0.1" + "tinyexec": "^1.0.2" }, "bin": { "nypm": "dist/cli.mjs" }, "engines": { - "node": "^14.16.0 || >=16.10.0" + "node": ">=18" } }, "node_modules/oas-kit-common": { @@ -6440,9 +6465,9 @@ } }, "node_modules/perfect-debounce": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.0.0.tgz", - "integrity": "sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", + "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", "dev": true, "license": "MIT" }, @@ -6479,13 +6504,13 @@ } }, "node_modules/playwright": { - "version": "1.58.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.0.tgz", - "integrity": "sha512-2SVA0sbPktiIY/MCOPX8e86ehA/e+tDNq+e5Y8qjKYti2Z/JG7xnronT/TXTIkKbYGWlCbuucZ6dziEgkoEjQQ==", + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", + "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.58.0" + "playwright-core": "1.58.2" }, "bin": { "playwright": "cli.js" @@ -6498,9 +6523,9 @@ } }, "node_modules/playwright-core": { - "version": "1.58.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.0.tgz", - "integrity": "sha512-aaoB1RWrdNi3//rOeKuMiS65UCcgOVljU46At6eFcOFPFHWtd2weHRRow6z/n+Lec0Lvu0k9ZPKJSjPugikirw==", + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", + "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", "dev": true, "license": "Apache-2.0", "bin": { @@ -7141,9 +7166,9 @@ } }, "node_modules/set-cookie-parser": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", - "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.0.1.tgz", + "integrity": "sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q==", "devOptional": true, "license": "MIT" }, @@ -7171,17 +7196,17 @@ } }, "node_modules/shiki": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.21.0.tgz", - "integrity": "sha512-N65B/3bqL/TI2crrXr+4UivctrAGEjmsib5rPMMPpFp1xAx/w03v8WZ9RDDFYteXoEgY7qZ4HGgl5KBIu1153w==", + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.22.0.tgz", + "integrity": "sha512-LBnhsoYEe0Eou4e1VgJACes+O6S6QC0w71fCSp5Oya79inkwkm15gQ1UF6VtQ8j/taMDh79hAB49WUk8ALQW3g==", "license": "MIT", "dependencies": { - "@shikijs/core": "3.21.0", - "@shikijs/engine-javascript": "3.21.0", - "@shikijs/engine-oniguruma": "3.21.0", - "@shikijs/langs": "3.21.0", - "@shikijs/themes": "3.21.0", - "@shikijs/types": "3.21.0", + "@shikijs/core": "3.22.0", + "@shikijs/engine-javascript": "3.22.0", + "@shikijs/engine-oniguruma": "3.22.0", + "@shikijs/langs": "3.22.0", + "@shikijs/themes": "3.22.0", + "@shikijs/types": "3.22.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } @@ -7348,9 +7373,9 @@ "license": "MIT" }, "node_modules/storybook": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/storybook/-/storybook-10.2.1.tgz", - "integrity": "sha512-hgiiwT4ZWJ/yrRpoXnHpCzWOsUvLUwQqgM/ws6mCIDsKJ7Gc7irL6DjWpi8G7l1Uq5VXYsQjXQo5ydb8Pyajdg==", + "version": "10.2.8", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-10.2.8.tgz", + "integrity": "sha512-885uSIn8NQw2ZG7vy84K45lHCOSyz1DVsDV8pHiHQj3J0riCuWLNeO50lK9z98zE8kjhgTtxAAkMTy5nkmNRKQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7503,9 +7528,9 @@ } }, "node_modules/svelte": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.49.0.tgz", - "integrity": "sha512-Fn2mCc3XX0gnnbBYzWOTrZHi5WnF9KvqmB1+KGlUWoJkdioPmFYtg2ALBr6xl2dcnFTz3Vi7/mHpbKSVg/imVg==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.50.0.tgz", + "integrity": "sha512-FR9kTLmX5i0oyeQ5j/+w8DuagIkQ7MWMuPpPVioW2zx9Dw77q+1ufLzF1IqNtcTXPRnIIio4PlasliVn43OnbQ==", "license": "MIT", "dependencies": { "@jridgewell/remapping": "^2.3.4", @@ -7518,7 +7543,7 @@ "clsx": "^2.1.1", "devalue": "^5.6.2", "esm-env": "^1.2.1", - "esrap": "^2.2.1", + "esrap": "^2.2.2", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", @@ -7574,9 +7599,9 @@ "license": "MIT" }, "node_modules/svelte-check": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.3.5.tgz", - "integrity": "sha512-e4VWZETyXaKGhpkxOXP+B/d0Fp/zKViZoJmneZWe/05Y2aqSKj3YN2nLfYPJBQ87WEiY4BQCQ9hWGu9mPT1a1Q==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.3.6.tgz", + "integrity": "sha512-uBkz96ElE3G4pt9E1Tw0xvBfIUQkeH794kDQZdAUk795UVMr+NJZpuFSS62vcmO/DuSalK83LyOwhgWq8YGU1Q==", "dev": true, "license": "MIT", "dependencies": { @@ -7709,18 +7734,18 @@ } }, "node_modules/svelte/node_modules/esrap": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.1.tgz", - "integrity": "sha512-GiYWG34AN/4CUyaWAgunGt0Rxvr1PTMlGC0vvEov/uOQYWne2bpN03Um+k8jT+q3op33mKouP2zeJ6OlM+qeUg==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.2.tgz", + "integrity": "sha512-zA6497ha+qKvoWIK+WM9NAh5ni17sKZKhbS5B3PoYbBvaYHZWoS33zmFybmyqpn07RLUxSmn+RCls2/XF+d0oQ==", "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "node_modules/svelte2tsx": { - "version": "0.7.46", - "resolved": "https://registry.npmjs.org/svelte2tsx/-/svelte2tsx-0.7.46.tgz", - "integrity": "sha512-S++Vw3w47a8rBuhbz4JK0fcGea8tOoX1boT53Aib8+oUO2EKeOG+geXprJVTDfBlvR+IJdf3jIpR2RGwT6paQA==", + "version": "0.7.47", + "resolved": "https://registry.npmjs.org/svelte2tsx/-/svelte2tsx-0.7.47.tgz", + "integrity": "sha512-1aw/MFKVPM96OBevJdC12do2an9t5Zwr3Va9amLgTLpJje36ibD1iIHpuqCYWUrdR9vw6g6btKGQPmsqE8ZYCw==", "dev": true, "license": "MIT", "dependencies": { @@ -7740,31 +7765,30 @@ "license": "ISC" }, "node_modules/swagger-typescript-api": { - "version": "13.2.16", - "resolved": "https://registry.npmjs.org/swagger-typescript-api/-/swagger-typescript-api-13.2.16.tgz", - "integrity": "sha512-PbjfCbNMx1mxqLamUpMA96fl2HJQh9Q5qRWyVRXmZslRJFHBkMfAj7OnEv6IOtSnmD+TXp073yBhKWiUxqeLhQ==", + "version": "13.2.17", + "resolved": "https://registry.npmjs.org/swagger-typescript-api/-/swagger-typescript-api-13.2.17.tgz", + "integrity": "sha512-kroHWv9sZBaltB3KlnYZWgWKQ2AmQ1fPoySr4sKirUBCIynwvCE1SlfJcfuFRIPq8z+smyfP4JpwrcqKo73Y8Q==", "dev": true, "license": "MIT", "dependencies": { - "@biomejs/js-api": "3.0.0", - "@biomejs/wasm-nodejs": "2.2.6", - "@types/lodash": "^4.17.20", + "@biomejs/js-api": "4.0.0", + "@biomejs/wasm-nodejs": "2.3.14", "@types/swagger-schema-official": "^2.0.25", - "c12": "^3.3.0", - "citty": "^0.1.6", + "c12": "^3.3.3", + "citty": "^0.2.0", "consola": "^3.4.2", + "es-toolkit": "^1.43.0", "eta": "^3.5.0", - "lodash": "^4.17.21", "nanoid": "^5.1.6", "openapi-types": "^12.1.3", "swagger-schema-official": "2.0.0-bab6bed", "swagger2openapi": "^7.0.8", "typescript": "~5.9.3", - "yaml": "^2.8.1" + "yaml": "^2.8.2" }, "bin": { - "sta": "dist/cli.js", - "swagger-typescript-api": "dist/cli.js" + "sta": "dist/cli.mjs", + "swagger-typescript-api": "dist/cli.mjs" }, "engines": { "node": ">=20" @@ -8127,6 +8151,16 @@ "typescript": ">=4.8.4 <6.0.0" } }, + "node_modules/undici": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.20.0.tgz", + "integrity": "sha512-MJZrkjyd7DeC+uPZh+5/YaMDxFiiEEaDgbUSVMXayofAkDWF1088CDo+2RPg7B1BuS1qf1vgNE7xqwPxE0DuSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/undici-types": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", @@ -8174,9 +8208,9 @@ } }, "node_modules/unist-util-visit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", - "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -8573,9 +8607,9 @@ } }, "node_modules/webidl-conversions": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz", - "integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -8590,27 +8624,28 @@ "license": "MIT" }, "node_modules/whatwg-mimetype": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", - "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", "dev": true, "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/whatwg-url": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz", - "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==", + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.0.tgz", + "integrity": "sha512-9CcxtEKsf53UFwkSUZjG+9vydAsFO4lFHBpJUtjBcoJOCJpKnSJNwCw813zrYJHpCJ7sgfbtOe0V5Ku7Pa1XMQ==", "dev": true, "license": "MIT", "dependencies": { + "@exodus/bytes": "^1.11.0", "tr46": "^6.0.0", - "webidl-conversions": "^8.0.0" + "webidl-conversions": "^8.0.1" }, "engines": { - "node": ">=20" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/which": { @@ -8688,9 +8723,9 @@ } }, "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", "dev": true, "license": "MIT", "engines": { diff --git a/src/Exceptionless.Web/ClientApp/package.json b/src/Exceptionless.Web/ClientApp/package.json index bfb309d7a..8007c29e2 100644 --- a/src/Exceptionless.Web/ClientApp/package.json +++ b/src/Exceptionless.Web/ClientApp/package.json @@ -22,42 +22,44 @@ "test": "npm run test:unit -- --run && npm run test:e2e", "storybook": "storybook dev -p 6006", "build-storybook": "storybook build", - "upgrade": "ncu -i" + "update": "ncu -i", + "update:shadcn": "npx shadcn-svelte@latest update", + "update:skills": "npx skills update" }, "devDependencies": { - "@chromatic-com/storybook": "^5.0.0", - "@eslint/compat": "^2.0.1", + "@chromatic-com/storybook": "^5.0.1", + "@eslint/compat": "^2.0.2", "@eslint/js": "^9.39.2", - "@iconify-json/lucide": "^1.2.87", - "@playwright/test": "^1.58.0", - "@storybook/addon-a11y": "^10.2.1", - "@storybook/addon-docs": "^10.2.1", + "@iconify-json/lucide": "^1.2.89", + "@playwright/test": "^1.58.2", + "@storybook/addon-a11y": "^10.2.8", + "@storybook/addon-docs": "^10.2.8", "@storybook/addon-svelte-csf": "^5.0.10", - "@storybook/sveltekit": "^10.2.1", + "@storybook/sveltekit": "^10.2.8", "@sveltejs/adapter-static": "^3.0.10", - "@sveltejs/kit": "^2.50.1", + "@sveltejs/kit": "^2.50.2", "@sveltejs/vite-plugin-svelte": "^6.2.4", "@tailwindcss/vite": "^4.1.18", - "@tanstack/eslint-plugin-query": "^5.91.3", + "@tanstack/eslint-plugin-query": "^5.91.4", "@testing-library/jest-dom": "^6.9.1", "@testing-library/svelte": "^5.3.1", "@types/eslint": "^9.6.1", - "@types/node": "^25.1.0", + "@types/node": "^25.2.2", "@types/throttle-debounce": "^5.0.2", "cross-env": "^10.1.0", "eslint": "^9.39.2", "eslint-config-prettier": "^10.1.8", - "eslint-plugin-perfectionist": "^5.4.0", - "eslint-plugin-storybook": "^10.2.1", + "eslint-plugin-perfectionist": "^5.5.0", + "eslint-plugin-storybook": "^10.2.8", "eslint-plugin-svelte": "^3.14.0", - "jsdom": "^27.4.0", + "jsdom": "^28.0.0", "prettier": "^3.8.1", "prettier-plugin-svelte": "^3.4.1", "prettier-plugin-tailwindcss": "^0.7.2", - "storybook": "^10.2.1", - "svelte": "^5.49.0", - "svelte-check": "^4.3.5", - "swagger-typescript-api": "^13.2.16", + "storybook": "^10.2.8", + "svelte": "^5.50.0", + "svelte-check": "^4.3.6", + "swagger-typescript-api": "^13.2.17", "tslib": "^2.8.1", "typescript": "^5.9.3", "typescript-eslint": "^8.54.0", @@ -69,15 +71,15 @@ "dependencies": { "@exceptionless/browser": "^3.1.0", "@exceptionless/fetchclient": "^0.44.0", - "@internationalized/date": "^3.10.1", + "@internationalized/date": "^3.11.0", "@lucide/svelte": "^0.563.1", "@tanstack/svelte-form": "^1.28.0", "@tanstack/svelte-query": "^6.0.18", - "@tanstack/svelte-query-devtools": "^6.0.3", + "@tanstack/svelte-query-devtools": "^6.0.4", "@tanstack/svelte-table": "^9.0.0-alpha.10", "@types/d3-scale": "^4.0.9", "@types/d3-shape": "^3.1.8", - "bits-ui": "^2.15.4", + "bits-ui": "^2.15.5", "clsx": "^2.1.1", "d3-scale": "^4.0.2", "dompurify": "^3.3.1", @@ -87,7 +89,7 @@ "oidc-client-ts": "^3.4.1", "pretty-ms": "^9.3.0", "runed": "^0.37.1", - "shiki": "^3.21.0", + "shiki": "^3.22.0", "svelte-sonner": "^1.0.7", "svelte-time": "^2.1.0", "tailwind-merge": "^3.4.0", diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/events-overview.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/events-overview.svelte index 244d7443a..c5151a01c 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/events-overview.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/events-overview.svelte @@ -16,12 +16,14 @@ import type { PersistentEvent } from '../models/index'; + import { getSessionId } from '../utils'; import Environment from './views/environment.svelte'; import Error from './views/error.svelte'; import ExtendedData from './views/extended-data.svelte'; import Overview from './views/overview.svelte'; import PromotedExtendedData from './views/promoted-extended-data.svelte'; import Request from './views/request.svelte'; + import SessionEvents from './views/session-events.svelte'; import TraceLog from './views/trace-log.svelte'; interface Props { @@ -37,7 +39,7 @@ return []; } - const tabs = ['Overview']; + const tabs: TabType[] = ['Overview']; if (hasErrorOrSimpleError(event)) { tabs.push('Exception'); } @@ -54,6 +56,10 @@ tabs.push('Trace Log'); } + if (getSessionId(event)) { + tabs.push('Session Events'); + } + if (!project) { return tabs; } @@ -128,9 +134,9 @@ > () {:else} - + - {/if} + {/if} {#if projectQuery.isSuccess} @@ -140,9 +146,9 @@ > {projectQuery.data.name} {:else} - + - + {/if} @@ -168,6 +174,8 @@ {:else if tab === 'Trace Log'} + {:else if tab === 'Session Events'} + {:else if tab === 'Extended Data'} {:else} @@ -177,14 +185,14 @@ {/each} {:else} - + {#each { length: 5 } as name, index (`${name}-${index}`)} - + - + {/each} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/log-level.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/log-level.svelte index 921967df9..d6678e589 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/log-level.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/log-level.svelte @@ -1,6 +1,8 @@ @@ -252,7 +237,7 @@ -

+
{#if event.data?.['@error']} {:else if event.data?.['@simple_error']} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/views/session-events.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/views/session-events.svelte new file mode 100644 index 000000000..55f7916bf --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/views/session-events.svelte @@ -0,0 +1,136 @@ + + +{#if !hasPremiumFeatures} + + + Premium Feature + Sessions are a premium feature. Upgrade your plan to view session events. + +{/if} + +
+ {#if isSessionStart} + + + + Duration + + + + + {#if event.data?.sessionend} + (ended ) + {/if} + + + {#if event.data?.sessionend} + (ended ) + {/if} + + + {#if userIdentity} + + User Identity + + {userIdentity} + + {/if} + {#if userName} + + User Name + + {userName} + + {/if} + + + {/if} + +

Session Events

+ + {#if sessionEventsQuery().isPending} +
+ {#each Array.from({ length: 5 }, (_, i) => i) as i (i)} + + {/each} +
+ {:else if sessionEventsQuery().isError} + + Error loading session events + {sessionEventsQuery().error?.message ?? 'Unknown error'} + + {:else if (sessionEventsQuery().data ?? []).length > 0} + + + + Summary + Session Time + + + + {#each sessionEventsQuery().data ?? [] as sessionEvent (sessionEvent.id)} + + + + {sessionEvent.id} + + + + + + + {/each} + + + {:else} +

No session events found.

+ {/if} +
diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/models/event-data.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/events/models/event-data.ts index 5ace934dd..1da46d594 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/events/models/event-data.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/models/event-data.ts @@ -1,5 +1,3 @@ -import { logLevels } from '../options'; - export interface EnvironmentInfo { architecture?: string; available_physical_memory?: number; @@ -129,36 +127,3 @@ export interface UserInfo { identity?: string; name?: string; } - -// TODO: Move to a helper. -export function getLogLevel(level?: LogLevel | null): LogLevel | null { - switch (level?.toLowerCase().trim()) { - case '0': - case 'false': - case 'no': - case 'off': - return 'off'; - case '1': - case 'trace': - case 'true': - case 'yes': - return 'trace'; - case 'debug': - return 'debug'; - case 'error': - return 'error'; - case 'fatal': - return 'fatal'; - case 'info': - return 'info'; - case 'warn': - return 'warn'; - default: - return level ?? null; - } -} - -export function getLogLevelDisplayName(level?: LogLevel | null): LogLevel | null { - const resolvedLevel = getLogLevel(level); - return logLevels.find((l) => l.value === resolvedLevel)?.label ?? level ?? null; -} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/utils/index.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/events/utils/index.ts new file mode 100644 index 000000000..ed1ccf549 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/utils/index.ts @@ -0,0 +1,76 @@ +import type { PersistentEvent } from '../models'; +import type { LogLevel } from '../models/event-data'; + +import { logLevels } from '../options'; + +export function getLogLevel(level?: LogLevel | null): LogLevel | null { + switch (level?.toLowerCase().trim()) { + case '0': + case 'false': + case 'no': + case 'off': + return 'off'; + case '1': + case 'trace': + case 'true': + case 'yes': + return 'trace'; + case 'debug': + return 'debug'; + case 'error': + return 'error'; + case 'fatal': + return 'fatal'; + case 'info': + return 'info'; + case 'warn': + return 'warn'; + default: + return level ?? null; + } +} + +export function getLogLevelDisplayName(level?: LogLevel | null): LogLevel | null { + const resolvedLevel = getLogLevel(level); + return logLevels.find((l) => l.value === resolvedLevel)?.label ?? level ?? null; +} + +/** + * Determine session ID from event. + * For session start events, use reference_id + * For other events, check @ref:session in data + * @param event + * @returns Session ID or undefined if not found + */ +export function getSessionId(event?: null | PersistentEvent): string | undefined { + if (!event) { + return undefined; + } + + // For session start events, use reference_id + if (event.type === 'session') { + return event.reference_id ?? undefined; + } + + // For other events, check @ref:session in data + return event.data?.['@ref:session'] as string | undefined; +} + +/** + * Returns the session duration in milliseconds for a given event. + * If the session has ended, uses the event value or the difference between sessionend and start. + * If the session is active, returns the duration from start to now. + */ +export function getSessionStartDuration(event: PersistentEvent): number { + if (event.data?.sessionend) { + if (event.value) { + return event.value * 1000; + } + if (event.date) { + return new Date(event.data.sessionend).getTime() - new Date(event.date).getTime(); + } + return 0; + } + // If session is active, duration is from start to now + return Date.now() - new Date(event.date).getTime(); +} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/organizations/components/dialogs/impersonate-organization-dialog.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/organizations/components/dialogs/impersonate-organization-dialog.svelte index 43047ca46..c953c5faa 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/organizations/components/dialogs/impersonate-organization-dialog.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/organizations/components/dialogs/impersonate-organization-dialog.svelte @@ -205,7 +205,7 @@ } }} > - + {#if paidFilter === undefined} All Plans {:else if paidFilter} @@ -232,7 +232,7 @@ } }} > - + {#if suspendedFilter === undefined} All Status {:else if suspendedFilter} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/organizations/components/dialogs/leave-organization-dialog.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/organizations/components/dialogs/leave-organization-dialog.svelte index 114995ee8..a2eab35d2 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/organizations/components/dialogs/leave-organization-dialog.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/organizations/components/dialogs/leave-organization-dialog.svelte @@ -21,7 +21,7 @@ Leave Organization - Are you sure you want to leave "{name}"? + Are you sure you want to leave "{name}"? diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/organizations/components/dialogs/remove-organization-dialog.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/organizations/components/dialogs/remove-organization-dialog.svelte index d035325b5..fda168a70 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/organizations/components/dialogs/remove-organization-dialog.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/organizations/components/dialogs/remove-organization-dialog.svelte @@ -21,7 +21,7 @@ Delete Organization - Are you sure you want to delete "{name}"? + Are you sure you want to delete "{name}"? diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/organizations/components/notifications/premium-upgrade-notification.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/organizations/components/notifications/premium-upgrade-notification.svelte index b6afa9254..46976f1a4 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/organizations/components/notifications/premium-upgrade-notification.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/organizations/components/notifications/premium-upgrade-notification.svelte @@ -8,15 +8,16 @@ interface Props extends NotificationProps { name: string; organizationId: string; + premiumFeatureName?: string; } - let { name, organizationId, ...restProps }: Props = $props(); + let { name, organizationId, premiumFeatureName = 'search', ...restProps }: Props = $props(); {name} is attempting to use a premium feature. Upgrade now - to enable search and other premium features! + to enable {premiumFeatureName} and other premium features! diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/organizations/components/organization-notifications.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/organizations/components/organization-notifications.svelte index 36a2208b2..4ff0af608 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/organizations/components/organization-notifications.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/organizations/components/organization-notifications.svelte @@ -22,6 +22,7 @@ ignoreFree?: boolean; isChatEnabled: boolean; openChat: () => void; + premiumFeatureName?: string; requiresPremium?: boolean; } diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/organizations/hooks/use-premium-feature.svelte.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/organizations/hooks/use-premium-feature.svelte.ts new file mode 100644 index 000000000..cc5c1a608 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/organizations/hooks/use-premium-feature.svelte.ts @@ -0,0 +1,44 @@ +import { getContext, onDestroy } from 'svelte'; + +const NOTIFICATION_CONTEXT_KEY = 'organizationNotifications'; + +interface Notification { + feature: string; + id: number; + message: string; + timestamp: number; + type: 'premium-feature'; +} + +/** + * Triggers a premium feature notification for the current organization. + * @param featureName Feature name to display in the notification. + */ +export function usePremiumFeature(featureName?: string) { + const notifications = getContext(NOTIFICATION_CONTEXT_KEY) as undefined | { update: (fn: (n: Notification[]) => Notification[]) => void }; + let notificationId: null | number = null; + + if (notifications && featureName) { + const id = Date.now() + Math.floor(Math.random() * 10000); + notificationId = id; + notifications.update((n: Notification[]) => [ + ...n, + { + feature: featureName, + id, + message: `The feature "${featureName}" is available on premium plans.`, + timestamp: Date.now(), + type: 'premium-feature' + } + ]); + } + + onDestroy(() => { + if (!notifications || notificationId == null) { + return; + } + + notifications.update((n: Notification[]) => n.filter((notification: Notification) => notification.id !== notificationId)); + notificationId = null; + }); +} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/projects/components/dialogs/remove-project-config-dialog.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/projects/components/dialogs/remove-project-config-dialog.svelte index 14ffc302c..8fca64b34 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/projects/components/dialogs/remove-project-config-dialog.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/projects/components/dialogs/remove-project-config-dialog.svelte @@ -21,7 +21,7 @@ Delete Configuration Value - Are you sure you want to delete "{name}"? + Are you sure you want to delete "{name}"? diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/projects/components/dialogs/remove-project-dialog.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/projects/components/dialogs/remove-project-dialog.svelte index 927171a7c..b807f102c 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/projects/components/dialogs/remove-project-dialog.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/projects/components/dialogs/remove-project-dialog.svelte @@ -21,7 +21,7 @@ Delete Project - Are you sure you want to delete "{name}"? + Are you sure you want to delete "{name}"? diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/projects/components/dialogs/reset-project-data-dialog.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/projects/components/dialogs/reset-project-data-dialog.svelte index a4274c1bc..27dc7f6a7 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/projects/components/dialogs/reset-project-data-dialog.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/projects/components/dialogs/reset-project-data-dialog.svelte @@ -21,8 +21,8 @@ Reset Project Data - Are you sure you want to reset all project data for "{name}"? - This action cannot be undone and will permanently erase all events, stacks, and associated data. + Are you sure you want to reset all project data for "{name}"? This + action cannot be undone and will permanently erase all events, stacks, and associated data. diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/projects/components/project-log-level.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/projects/components/project-log-level.svelte index 1cc2dcef8..facb79ab1 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/projects/components/project-log-level.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/projects/components/project-log-level.svelte @@ -1,8 +1,10 @@ + +
+ {#if isLoading} + + {:else} + + d.sessions)))]} + {series} + axis={false} + grid={false} + brush={{ + onBrushEnd: (detail) => { + const [start, end] = detail.xDomain ?? []; + if (start instanceof Date && end instanceof Date) { + onRangeSelect?.(start, end); + } + } + }} + props={{ + area: { + curve: curveLinear + }, + canvas: { + class: 'cursor-crosshair' + }, + svg: { + class: 'cursor-crosshair' + } + }} + > + {#snippet tooltip()} + formatDateLabel(v as Date)} /> + {/snippet} + + + {/if} +
diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/sessions/components/sessions-stats-dashboard.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/sessions/components/sessions-stats-dashboard.svelte new file mode 100644 index 000000000..4f6724269 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/sessions/components/sessions-stats-dashboard.svelte @@ -0,0 +1,87 @@ + + +
+ + + Sessions + + + + {#if isLoading} + + {:else} +
+ +
+ {/if} +
+
+ + + + Sessions Per Hour + + + + {#if isLoading} + + {:else} +
+ {avgPerHour?.toFixed(1) ?? '0'} +
+ {/if} +
+
+ + + + Users + + + + {#if isLoading} + + {:else} +
+ +
+ {/if} +
+
+ + + + Average Duration + + + + {#if isLoading} + + {:else} +
+ + +
+ {/if} +
+
+
diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-multi-select.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-multi-select.svelte index 66837ed1c..15b9fa5ca 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-multi-select.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-multi-select.svelte @@ -120,11 +120,11 @@ onValueSelected(option.value)} value={option.value}>
- +
{option.label} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/logo.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/logo.svelte index d853629e1..525ad154d 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/logo.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/logo.svelte @@ -8,5 +8,5 @@ let { class: className, ...props }: HTMLAttributes = $props(); -Exceptionless Logo - +Exceptionless Logo + diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/calendar/calendar-month-select.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/calendar/calendar-month-select.svelte index 8d88deb55..664afab8d 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/calendar/calendar-month-select.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/calendar/calendar-month-select.svelte @@ -18,7 +18,11 @@ className )} > - + {#snippet child({ props, monthItems, selectedMonthItem })} {#each yearItems as yearItem (yearItem.value)} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/field/field-error.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/field/field-error.svelte index 1d5cc5f59..94a46c2bf 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/field/field-error.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/field/field-error.svelte @@ -19,7 +19,7 @@ if (children) return true; // no errors - if (!errors) return false; + if (!errors || errors.length === 0) return false; // has an error but no message if (errors.length === 1 && !errors[0]?.message) { diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/pagination/index.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/pagination/index.ts index dbe47abfc..d4b0b642a 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/pagination/index.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/pagination/index.ts @@ -16,10 +16,10 @@ export { Item, Link, PrevButton, //old - NextButton, //old - Ellipsis, - Previous, - Next, + NextButton, //old + Ellipsis, + Previous, + Next, // Root as Pagination, Content as PaginationContent, @@ -27,8 +27,8 @@ export { Item as PaginationItem, Link as PaginationLink, PrevButton as PaginationPrevButton, //old - NextButton as PaginationNextButton, //old - Ellipsis as PaginationEllipsis, - Previous as PaginationPrevious, - Next as PaginationNext + NextButton as PaginationNextButton, //old + Ellipsis as PaginationEllipsis, + Previous as PaginationPrevious, + Next as PaginationNext, }; diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/select/select-group.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/select/select-group.svelte index 5454fdb39..a1f43bf3d 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/select/select-group.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/select/select-group.svelte @@ -4,4 +4,4 @@ let { ref = $bindable(null), ...restProps }: SelectPrimitive.GroupProps = $props(); - + diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/separator/separator.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/separator/separator.svelte index e11a6f5ba..f40999fa7 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/separator/separator.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/separator/separator.svelte @@ -14,7 +14,7 @@ bind:ref data-slot={dataSlot} class={cn( - "bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px", + "bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:min-h-full data-[orientation=vertical]:w-px", className )} {...restProps} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/tooltip/tooltip-content.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/tooltip/tooltip-content.svelte index 788ec34db..266252287 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/tooltip/tooltip-content.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/tooltip/tooltip-content.svelte @@ -37,7 +37,7 @@ {#snippet child({ props })}
{ }); }); }); + + describe('Elastic date-math unit enforcement (lowercase d)', () => { + it('should accept now-7d as a valid expression', () => { + const result = parseDateMath('now-7d'); + expect(result.success).toBe(true); + expect(result.date).toEqual(new Date('2025-09-13T14:30:00Z')); + }); + + it('should reject now-7D (uppercase D is not a valid Elastic unit)', () => { + const result = parseDateMath('now-7D'); + expect(result.success).toBe(false); + }); + + it('should accept now-1d and resolve correctly', () => { + const result = parseDateMath('now-1d'); + expect(result.success).toBe(true); + expect(result.date).toEqual(new Date('2025-09-19T14:30:00Z')); + }); + + it('should reject now-1D (uppercase D)', () => { + const result = parseDateMath('now-1D'); + expect(result.success).toBe(false); + }); + + it('should parse [now-7d TO now] range correctly', () => { + const range = parseDateMathRange('[now-7d TO now]'); + expect(range.start).toEqual(new Date('2025-09-13T14:30:00Z')); + expect(range.end).toEqual(new Date('2025-09-20T14:30:00Z')); + }); + + it('should fail to parse [now-7D TO now] range (uppercase D)', () => { + const range = parseDateMathRange('[now-7D TO now]'); + // Should fall back to default range since uppercase D is invalid + expect(range.start).toEqual(new Date('2012-02-01')); + expect(range.end.getTime()).toBeCloseTo(new Date().getTime(), -3); + }); + + it('should accept rounding with lowercase d: now/d', () => { + const result = parseDateMath('now/d'); + expect(result.success).toBe(true); + }); + + it('should accept now-1d/d (subtraction + rounding with lowercase d)', () => { + const result = parseDateMath('now-1d/d'); + expect(result.success).toBe(true); + expect(result.date).toEqual(new Date('2025-09-19T00:00:00Z')); + }); + + it('should only accept documented Elastic units: y, M, w, d, h, H, m, s', () => { + const validUnits = ['y', 'M', 'w', 'd', 'h', 'H', 'm', 's']; + validUnits.forEach((unit) => { + const result = parseDateMath(`now-1${unit}`); + expect(result.success).toBe(true); + }); + }); + }); }); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/utils/datemath.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/utils/datemath.ts index 35fc4b64b..001e7d272 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/utils/datemath.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/utils/datemath.ts @@ -34,10 +34,10 @@ export const TIME_UNIT_NAMES: Record = { * Enhanced to match all backend test cases exactly */ const DATE_MATH_REGEX = - /^(?now|\*|(?\d{4}-?\d{2}-?\d{2}(?:[T\s](?:\d{1,2}(?::?\d{2}(?::?\d{2})?)?(?:\.\d{1,3})?)?(?:[+-]\d{2}:?\d{2}|Z)?)?)(?:\|\|)?)(?(?:[+\-/]\d*[yMwdhHms])*)$/i; + /^(?now|\*|(?\d{4}-?\d{2}-?\d{2}(?:[T\s](?:\d{1,2}(?::?\d{2}(?::?\d{2})?)?(?:\.\d{1,3})?)?(?:[+-]\d{2}:?\d{2}|Z)?)?)(?:\|\|)?)(?(?:[+\-/]\d*[yMwdhHms])*)$/; /** Pre-compiled regex for operation parsing - more strict validation */ -const OPERATION_REGEX = /([+\-/])(\d*)([yMwdhHms])/gi; +const OPERATION_REGEX = /([+\-/])(\d*)([yMwdhHms])/g; /** Result of parsing a date math expression */ export interface DateMathResult { diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/tokens/components/table/options.svelte.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/tokens/components/table/options.svelte.ts index 82f18c656..171110119 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/tokens/components/table/options.svelte.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/tokens/components/table/options.svelte.ts @@ -18,7 +18,7 @@ export function getColumns(): ColumnDef[] { enableSorting: false, header: 'API Key', meta: { - class: 'w-[180px]' + class: 'w-45' } }, { @@ -28,7 +28,7 @@ export function getColumns(): ColumnDef[] { enableSorting: false, header: 'Notes', meta: { - class: 'w-[200px]' + class: 'w-50' } }, { diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/users/components/dialogs/remove-user-dialog.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/users/components/dialogs/remove-user-dialog.svelte index 58fa5748e..cd393253d 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/users/components/dialogs/remove-user-dialog.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/users/components/dialogs/remove-user-dialog.svelte @@ -21,7 +21,7 @@ Remove User - Are you sure you want to remove "{name}"? + Are you sure you want to remove "{name}"? diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/users/components/table/options.svelte.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/users/components/table/options.svelte.ts index c717ba9bc..c41834776 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/users/components/table/options.svelte.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/users/components/table/options.svelte.ts @@ -18,7 +18,7 @@ export function getColumns(organizationId: string): Colu enableSorting: false, header: 'Email Address', meta: { - class: 'w-[200px]' + class: 'w-50' } }, { @@ -28,7 +28,7 @@ export function getColumns(organizationId: string): Colu enableSorting: false, header: 'Name', meta: { - class: 'w-[200px]' + class: 'w-50' } }, { @@ -38,7 +38,7 @@ export function getColumns(organizationId: string): Colu enableSorting: false, header: 'Invited', meta: { - class: 'w-[100px]' + class: 'w-25' } } ]; diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/webhooks/components/dialogs/remove-webhook-dialog.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/webhooks/components/dialogs/remove-webhook-dialog.svelte index 3c6f5bbf5..ecda54b2d 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/webhooks/components/dialogs/remove-webhook-dialog.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/webhooks/components/dialogs/remove-webhook-dialog.svelte @@ -21,7 +21,7 @@ Delete Webhook - Are you sure you want to delete "{url}"? + Are you sure you want to delete "{url}"? diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/webhooks/components/table/options.svelte.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/webhooks/components/table/options.svelte.ts index 0ddb02f88..8c5b84c00 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/webhooks/components/table/options.svelte.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/webhooks/components/table/options.svelte.ts @@ -17,7 +17,7 @@ export function getColumns(): ColumnDef[] { enableSorting: false, header: 'Url', meta: { - class: 'w-[200px]' + class: 'w-50' } }, { @@ -27,7 +27,7 @@ export function getColumns(): ColumnDef[] { enableSorting: false, header: 'Event Types', meta: { - class: 'w-[200px]' + class: 'w-50' } }, { diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/sidebar.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/sidebar.svelte index 850e52fed..38f388ff9 100644 --- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/sidebar.svelte +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/sidebar.svelte @@ -19,6 +19,7 @@ let { footer, header, impersonating = false, routes, ...props }: Props = $props(); const dashboardRoutes = $derived(routes.filter((route) => route.group === 'Dashboards')); + const reportRoutes = $derived(routes.filter((route) => route.group === 'Reports')); // Settings routes need additional filtering based on navigation context const navigationContext: NavigationItemContext = $derived({ authenticated: true, impersonating }); @@ -61,6 +62,27 @@ + {#if reportRoutes.length > 0} + + Reports + + {#each reportRoutes as route (route.href)} + {@const Icon = route.icon} + + + {#snippet child({ props })} + + + {route.title} + + {/snippet} + + + {/each} + + + {/if} + diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/+layout.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/+layout.svelte index f69ca9ab0..d437e73ba 100644 --- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/+layout.svelte +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/+layout.svelte @@ -170,7 +170,13 @@ const organizations = $derived(organizationsQuery.data?.data ?? []); const impersonatingOrganizationId = $derived.by(() => { - const isUserOrganization = meQuery.data?.organization_ids.includes(organization.current ?? ''); + // Only consider impersonation if user data is loaded and user has organizations + const userOrgIds = meQuery.data?.organization_ids; + if (!userOrgIds || userOrgIds.length === 0 || !organization.current) { + return undefined; + } + + const isUserOrganization = userOrgIds.includes(organization.current); return isUserOrganization ? undefined : organization.current; }); diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/+page.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/+page.svelte index 403c2a72d..b4351ba2d 100644 --- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/+page.svelte +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/+page.svelte @@ -282,7 +282,7 @@ {#snippet footerChildren()} -
+
{#if table.getSelectedRowModel().flatRows.length} {/if} diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/account/appearance/(components)/theme-preview.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/account/appearance/(components)/theme-preview.svelte index 30a65c312..796ea9e37 100644 --- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/account/appearance/(components)/theme-preview.svelte +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/account/appearance/(components)/theme-preview.svelte @@ -18,15 +18,15 @@
-
+
-
+
-
+
@@ -38,15 +38,15 @@
-
+
-
+
-
+
diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/issues/+page.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/issues/+page.svelte index c80f9439d..2f66c284e 100644 --- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/issues/+page.svelte +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/issues/+page.svelte @@ -311,7 +311,7 @@ {#snippet footerChildren()} -
+
diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/organization/[organizationId]/usage/+page.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/organization/[organizationId]/usage/+page.svelte index 2fd5a2ad5..190b26702 100644 --- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/organization/[organizationId]/usage/+page.svelte +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/organization/[organizationId]/usage/+page.svelte @@ -84,7 +84,7 @@ {#if organizationQuery.isLoading}
- +
{:else if organizationQuery.error} diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/project/[projectId]/usage/+page.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/project/[projectId]/usage/+page.svelte index 97b665b87..14474fec0 100644 --- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/project/[projectId]/usage/+page.svelte +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/project/[projectId]/usage/+page.svelte @@ -112,7 +112,7 @@ {#if projectQuery.isLoading || organizationQuery.isLoading}
- +
{:else if projectQuery.error || organizationQuery.error} @@ -145,7 +145,7 @@

- + + import type { GetEventsParams } from '$features/events/api.svelte'; + import type { EventSummaryModel, SummaryTemplateKeys } from '$features/events/components/summary/index'; + + import { resolve } from '$app/paths'; + import { page } from '$app/state'; + import * as DataTable from '$comp/data-table'; + import DataTableViewOptions from '$comp/data-table/data-table-view-options.svelte'; + import * as FacetedFilter from '$comp/faceted-filter'; + import RefreshButton from '$comp/refresh-button.svelte'; + import { A, H3 } from '$comp/typography'; + import * as Alert from '$comp/ui/alert'; + import { Button } from '$comp/ui/button'; + import { Label } from '$comp/ui/label'; + import * as Sheet from '$comp/ui/sheet'; + import { Switch } from '$comp/ui/switch'; + import EventsOverview from '$features/events/components/events-overview.svelte'; + import { DateFilter, ProjectFilter, TypeFilter } from '$features/events/components/filters'; + import { + applyTimeFilter, + buildFilterCacheKey, + filterCacheVersionNumber, + filterChanged, + filterRemoved, + getFiltersFromCache, + toFilter, + updateFilterCache + } from '$features/events/components/filters/helpers.svelte'; + import OrganizationDefaultsFacetedFilterBuilder from '$features/events/components/filters/organization-defaults-faceted-filter-builder.svelte'; + import EventsDataTable from '$features/events/components/table/events-data-table.svelte'; + import { getColumns } from '$features/events/components/table/options.svelte'; + import { getOrganizationQuery } from '$features/organizations/api.svelte'; + import { organization } from '$features/organizations/context.svelte'; + import { getOrganizationSessionsCountQuery } from '$features/sessions/api.svelte'; + import SessionsDashboardChart from '$features/sessions/components/sessions-dashboard-chart.svelte'; + import SessionsStatsDashboard from '$features/sessions/components/sessions-stats-dashboard.svelte'; + import * as agg from '$features/shared/api/aggregations'; + import { getSharedTableOptions, isTableEmpty, removeTableData, removeTableSelection } from '$features/shared/table.svelte'; + import { fillDateSeries } from '$features/shared/utils/charts.js'; + import { parseDateMathRange, toDateMathRange } from '$features/shared/utils/datemath'; + import { ChangeType, type WebSocketMessageValue } from '$features/websockets/models'; + import { DEFAULT_LIMIT, DEFAULT_OFFSET, useFetchClientStatus } from '$shared/api/api.svelte'; + import { type FetchClientResponse, useFetchClient } from '@exceptionless/fetchclient'; + import ExternalLink from '@lucide/svelte/icons/external-link'; + import InfoIcon from '@lucide/svelte/icons/info'; + import { createTable } from '@tanstack/svelte-table'; + import { queryParamsState } from 'kit-query-params'; + import { useEventListener, watch } from 'runed'; + import { throttle } from 'throttle-debounce'; + + let selectedEventId: null | string = $state(null); + function rowclick(row: EventSummaryModel) { + selectedEventId = row.id; + } + + function rowHref(row: EventSummaryModel): string { + return resolve('/(app)/event/[eventId]', { eventId: row.id }); + } + + // Organization query to check premium features + const organizationQuery = getOrganizationQuery({ + route: { + get id() { + return organization.current; + } + } + }); + + const hasPremiumFeatures = $derived(organizationQuery.data?.has_premium_features ?? false); + + // View Active toggle state + let viewActive = $state(false); + + const DEFAULT_TIME_RANGE = '[now-7d TO now]'; + const DEFAULT_FILTERS = [new DateFilter('date', DEFAULT_TIME_RANGE), new ProjectFilter([]), new TypeFilter(['session'])]; + const DEFAULT_PARAMS = { + filter: 'type:session', + limit: DEFAULT_LIMIT, + time: DEFAULT_TIME_RANGE + }; + + function filterCacheKey(filter: null | string): string { + return buildFilterCacheKey(organization.current, page.url.pathname, filter); + } + + updateFilterCache(filterCacheKey(DEFAULT_PARAMS.filter), DEFAULT_FILTERS); + const queryParams = queryParamsState({ + default: DEFAULT_PARAMS, + pushHistory: true, + schema: { + filter: 'string', + limit: 'number', + time: 'string' + } + }); + + watch( + () => organization.current, + () => { + updateFilterCache(filterCacheKey(DEFAULT_PARAMS.filter), DEFAULT_FILTERS); + Object.assign(queryParams, DEFAULT_PARAMS); + reset(); + }, + { lazy: true } + ); + + let filters = $state(applyTimeFilter(getFiltersFromCache(filterCacheKey(queryParams.filter), queryParams.filter), queryParams.time)); + watch( + [() => queryParams.filter, () => queryParams.time, () => filterCacheVersionNumber()], + ([filter, time]) => { + filters = applyTimeFilter(getFiltersFromCache(filterCacheKey(filter), filter), time); + }, + { lazy: true } + ); + + $effect(() => { + queryParams.limit ??= DEFAULT_LIMIT; + }); + + function onFilterChanged(addedOrUpdated: FacetedFilter.IFilter): void { + updateFilters(filterChanged(filters ?? [], addedOrUpdated)); + selectedEventId = null; + } + + function onFilterRemoved(removed?: FacetedFilter.IFilter): void { + updateFilters(filterRemoved(filters ?? [], removed)); + } + + function updateFilters(updatedFilters: FacetedFilter.IFilter[]): void { + const filter = toFilter(updatedFilters.filter((f) => f.type !== 'date')); + + updateFilterCache(filterCacheKey(filter), updatedFilters); + queryParams.time = (updatedFilters.find((f) => f.type === 'date') as DateFilter)?.value as string; + queryParams.filter = filter; + } + + // Build filter with active sessions toggle + function activeFilter(): string { + let filter = queryParams.filter ?? 'type:session'; + if (viewActive) { + filter += ' _missing_:data.sessionend'; + } + return filter; + } + + const eventsQueryParameters: GetEventsParams = $state({ + get filter() { + return activeFilter(); + }, + set filter(value) { + queryParams.filter = value; + }, + get limit() { + return queryParams.limit!; + }, + set limit(value) { + queryParams.limit = value; + }, + mode: 'summary', + offset: DEFAULT_OFFSET, + get time() { + return queryParams.time!; + }, + set time(value) { + queryParams.time = value; + } + }); + + const client = useFetchClient(); + const clientStatus = useFetchClientStatus(client); + let clientResponse = $state[]>>(); + + const table = createTable( + getSharedTableOptions>({ + columnPersistenceKey: 'sessions-column-visibility', + get columns() { + return getColumns>(eventsQueryParameters.mode); + }, + paginationStrategy: 'cursor', + get queryData() { + return clientResponse?.data ?? []; + }, + get queryMeta() { + return clientResponse?.meta; + }, + get queryParameters() { + return eventsQueryParameters; + } + }) + ); + + const canRefresh = $derived(!table.getIsSomeRowsSelected() && !table.getIsAllRowsSelected() && table.getState().pagination.pageIndex === 0); + + function reset() { + table.resetRowSelection(); + table.setPageIndex(0); + } + + async function handleRefresh() { + if (!canRefresh) { + reset(); + } + + await loadData(); + } + + async function loadData() { + if (client.isLoading || !organization.current) { + return; + } + + clientResponse = await client.getJSON[]>(`organizations/${organization.current}/events/sessions`, { + params: eventsQueryParameters as Record + }); + } + + const throttledLoadData = throttle(10000, loadData); + + async function onPersistentEventChanged(message: WebSocketMessageValue<'PersistentEventChanged'>) { + if (message.id && message.change_type === ChangeType.Removed) { + removeTableSelection(table, message.id); + + if (removeTableData(table, (doc) => doc.id === message.id)) { + if (isTableEmpty(table)) { + await throttledLoadData(); + return; + } + } + } + } + + useEventListener(document, 'PersistentEventChanged', async (event) => await onPersistentEventChanged((event as CustomEvent).detail)); + + $effect(() => { + loadData(); + }); + + // Session stats query with aggregations + const statsQuery = getOrganizationSessionsCountQuery({ + params: { + get aggregations() { + return `avg:value cardinality:user date:(date${DEFAULT_OFFSET ? `^${DEFAULT_OFFSET}` : ''} cardinality:user)`; + }, + get filter() { + return activeFilter(); + }, + get time() { + return eventsQueryParameters.time; + } + }, + route: { organizationId: organization.current } + }); + + // Compute stats from aggregations + const stats = $derived.by(() => { + if (!statsQuery.data?.aggregations) { + return { + avgDuration: 0, + avgPerHour: 0, + totalSessions: 0, + totalUsers: 0 + }; + } + + const avgValue = agg.average(statsQuery.data.aggregations, 'avg_value')?.value ?? 0; + const cardinalityUser = agg.cardinality(statsQuery.data.aggregations, 'cardinality_user')?.value ?? 0; + const total = statsQuery.data.total ?? 0; + + // Calculate avg per hour based on time range + const timeRange = parseDateMathRange(queryParams.time); + const hours = (timeRange.end.getTime() - timeRange.start.getTime()) / (1000 * 60 * 60); + const avgPerHour = hours > 0 ? total / hours : 0; + + return { + avgDuration: avgValue, + avgPerHour, + totalSessions: total, + totalUsers: cardinalityUser + }; + }); + + // Chart data from date histogram + const chartData = $derived(() => { + const timeRange = parseDateMathRange(queryParams.time); + + const buildZeroFilledSeries = () => + fillDateSeries(timeRange.start, timeRange.end, (date: Date) => ({ + date, + sessions: 0, + users: 0 + })); + + if (!statsQuery.data?.aggregations) { + return buildZeroFilledSeries(); + } + + const dateHistogramBuckets = agg.dateHistogram(statsQuery.data.aggregations, 'date_date')?.buckets ?? []; + if (dateHistogramBuckets.length === 0) { + return buildZeroFilledSeries(); + } + + return dateHistogramBuckets.map((bucket) => ({ + date: new Date(bucket.key), + sessions: bucket.total ?? 0, + users: agg.cardinality(bucket.aggregations, 'cardinality_user')?.value ?? 0 + })); + }); + + function onRangeSelect(start: Date, end: Date) { + onFilterChanged(new DateFilter('date', toDateMathRange(start, end))); + } + + function handleUpgrade() { + // Navigate to billing page + if (organization.current) { + window.location.href = resolve('/(app)/organization/[organizationId]/billing', { organizationId: organization.current }); + } + } + + +
+ {#if organizationQuery.isSuccess && !hasPremiumFeatures} + + + Premium Feature + + Upgrade now to enable sessions and other premium features! + + + {/if} + +
+

Sessions

+
+ + + +
+
+
+ + +
+ + +
+
+ +
+ + + + + + {#snippet footerChildren()} + + +
+ + +
+ {/snippet} +
+
+
+ + (selectedEventId = null)} open={!!selectedEventId}> + + + Event Details + +
+ (selectedEventId = null)} /> +
+
+