Commit graph

63 commits

Author SHA1 Message Date
Hibryda
d334f57e9e fix(electrobun): register agent message types in RPC (was empty, messages silently dropped) 2026-03-26 02:21:37 +01:00
Hibryda
4a83472392 chore(electrobun): add parseMessage debug logging to trace agent message forwarding 2026-03-26 02:10:22 +01:00
Hibryda
a1a5467ef9 fix(electrobun): copy sidecar/dist into build output + fix runner path resolution 2026-03-26 02:06:39 +01:00
Hibryda
541b6eda46 fix(electrobun): sidecar runner path resolution for dev build output + debug logging 2026-03-26 02:04:02 +01:00
Hibryda
66dce7ebae fix(electrobun): 7 bug fixes + 3 partial features completed
Bugs fixed:
- Agent retry: clear dead session on failed start (was non-recoverable)
- seqId persist: save seq_id column in agent_messages, migrate existing DBs
- Window frame save: wire resize event listener to saveWindowFrame()
- Diagnostics counters: real RPC call counter via withRpcCounting() proxy

Partial features completed:
- Search auto-seed: rebuildIndex() on startup + indexMessage() on save
- Notifications: removed 3 hardcoded demo entries, start empty
- Agent cost streaming: emitCostIfChanged() on every message batch

New: src/bun/rpc-stats.ts (RPC call + dropped event counters)
2026-03-25 20:26:49 +01:00
Hibryda
61466b53c6 chore(electrobun): revert to real app mode (resize test flags disabled) 2026-03-25 18:11:59 +01:00
Hibryda
f97eaa1c36 fix(electrobun): update stub HTML + 800x600 test window size 2026-03-25 17:33:33 +01:00
Hibryda
e5125e6e6e fix(electrobun): enable stub page + deferred native resize for testing 2026-03-25 17:18:09 +01:00
Hibryda
449fa3dcae feat(electrobun): native C resize lib + stub test page + deferred init
- agor_resize.c: C library with GTK signal handlers for resize
- native-resize.ts: Bun FFI wrapper for libagor-resize.so
- resize-test.html: minimal stub for isolating resize behavior
- index.ts: native resize disabled pending stability testing
- RESIZE_TEST env var for loading stub page via Vite
2026-03-25 17:01:11 +01:00
Hibryda
178e560068 feat(electrobun): native C resize library (Wails pattern)
New: agor-pty/native/agor_resize.c — C shared library that:
- Connects button-press-event on GtkWindow + WebView children in C
- Stores mouseButton, xroot, yroot, dragTime from real GTK events
- Exports agor_resize_start(edge) for Bun FFI

Build: gcc -shared -fPIC -o libagor-resize.so agor_resize.c $(pkg-config --cflags --libs gtk+-3.0)

New: ui-electrobun/src/bun/native-resize.ts — Bun FFI wrapper
App.svelte: simplified to just edge detection + RPC call
2026-03-25 16:44:51 +01:00
Hibryda
de40bcbcac fix(electrobun): disable all GTK FFI, JS-only resize via setFrame RPC
Bun segfaults (address 0x10) from prior GTK widget tree modifications
(wrapWebViewInScrolledWindow corrupted the tree). Disabled all GTK FFI
except begin_move_drag (which works). JS handles capture mouse events,
compute delta, and call window.setFrame RPC.

Clean build required: rm -rf ui-electrobun/build/

Status: resize outward works, resize inward blocked by WebView min-size.
Next: need C shared library for proper GTK signal connection.
2026-03-25 16:26:00 +01:00
Hibryda
b9169ec046 Revert "fix(electrobun): connect button-press on WebView child too (events go to deepest child)"
This reverts commit 74ec191ce9.
2026-03-25 16:15:55 +01:00
Hibryda
74ec191ce9 fix(electrobun): connect button-press on WebView child too (events go to deepest child) 2026-03-25 16:13:37 +01:00
Hibryda
e563da2b3b fix(electrobun): re-enable CSS resize handles + GTK button-press only
Motion handler crashes in Bun's JSCallback FFI bridge — disabled.
CSS handles provide cursor feedback. GTK button-press-event handles
actual resize via begin_resize_drag with real event timestamp.

Bundler issue resolved: syntax error in /* */ comment was preventing
Electrobun's bundler from compiling gtk-resize.ts.
2026-03-25 16:12:15 +01:00
Hibryda
828e97276a fix(electrobun): fix gtk-resize.ts syntax error + Buffer-based FFI pointers
Fixed malformed block comment that prevented bun bundler from compiling.
Switched from TypedArray.buffer to Buffer.alloc for FFI output pointers.
Disabled motion handler to isolate button-press resize.

NOTE: Electrobun's dev command rebuilds the bundle on every run, overwriting
manual builds. To test changes, run: bun build src/bun/index.ts --outfile
build/.../app/bun/index.js --target=bun --external=electrobun AFTER
electrobun dev finishes building, then restart the app.
2026-03-25 15:59:51 +01:00
Hibryda
e9fcd8401e feat(electrobun): native GTK resize via button-press-event signal (tao pattern)
All previous approaches failed because they initiated resize from JS
(too late, wrong timestamp, WebKit steals grab). The correct approach
(proven by Tauri/tao) is to connect button-press-event DIRECTLY on the
GtkWindow at the GTK level, BEFORE WebKitGTK processes events.

New: gtk-resize.ts
- JSCallback for button-press-event + motion-notify-event
- Hit-test in 8px border zone (same as tao's 5*scale_factor)
- begin_resize_drag with REAL event timestamp (not GDK_CURRENT_TIME)
- Returns TRUE to STOP propagation (WebKit never sees the press)
- Cursor updates on motion-notify in border zone

Removed: all JS resize handles (divs, mousemove, mouseup, RPC calls)
2026-03-25 15:25:25 +01:00
Hibryda
0e6408a447 fix(electrobun): X11 XMoveResizeWindow bypass for resize (no GTK involvement)
begin_resize_drag + XUngrabPointer still fails because GTK's layout
cycle re-asserts WebView preferred size, fighting the WM resize.

New approach: JS mousemove → XMoveResizeWindow via libX11.so.6 FFI.
Completely bypasses GTK size negotiation. GTK only receives
ConfigureNotify after the X server has already resized the window.

Added: x11SetFrame() using gdk_x11_display_get_xdisplay +
gdk_x11_window_get_xid + XMoveResizeWindow.
2026-03-25 14:19:27 +01:00
Hibryda
058ae563d5 fix(electrobun): XUngrabPointer before begin_resize_drag (SDL2 pattern)
Root cause confirmed via X11 research: WebKitGTK holds an implicit X11
pointer grab from button-press. GTK's gdk_seat_ungrab targets the wrong
device, leaving the grab active. Mutter gets AlreadyGrabbed and gives up.

Fix: call XUngrabPointer(xdisplay, CurrentTime) + XFlush directly via
libX11.so.6 FFI before begin_resize_drag. This releases ALL grabs on
the display connection, matching SDL2's proven approach.

Removed failed approaches: WebView sensitivity toggle, findWebView cache.
2026-03-25 14:06:45 +01:00
Hibryda
7bb08697d6 fix(electrobun): disable WebView sensitivity during resize to prevent grab loss
Codex review #3 identified: the issue is NOT min-size but pointer grab
conflict. WebKitGTK steals the WM's X11 pointer grab when cursor enters
the WebView during inward resize.

Fix: gtk_widget_set_sensitive(webview, false) before begin_resize_drag,
re-enable after 5s. Also added findWebView() to cache the deepest
GtkBin child pointer for fast access.
2026-03-25 13:47:05 +01:00
Hibryda
300bd30ca3 fix(electrobun): wrap WebView in GtkScrolledWindow for resize-in
Codex review #2 found: set_size_request only controls minimum, not
natural/preferred size. WebView still reports content size as natural,
causing rubber-band effect during shrink.

Fix: reparent WebKitWebView into GtkScrolledWindow via FFI with
propagate-natural-width/height=FALSE and min-content=1x1. This
decouples WebView content size from window size negotiation.

Also reverted to native begin_resize_drag (WM handles everything).
2026-03-25 13:38:29 +01:00
Hibryda
d1583f8ce4 fix(electrobun): set_size_request(1,1) not (-1,-1) + revert to begin_resize_drag
Codex review found: set_size_request(-1,-1) means "use preferred size"
which RE-ENABLES WebView content-based minimum. Using (1,1) FORCES a
1x1 minimum, actually overriding the preferred size.

Reverted to native begin_resize_drag (WM handles resize smoothly).
Fixed onResizeStart sync: e.stopPropagation() now runs BEFORE any async
work, preventing sidebar drag handler from intercepting.

Removed JS mousemove resize loop — native GTK resize is correct approach.
2026-03-25 13:24:41 +01:00
Hibryda
fd2f626c20 fix(electrobun): GTK FFI direct resize via gtk_window_resize/move
Electrobun's setSize respects WebView min-size constraint. Bypass it
with direct gtk_window_resize() + gtk_window_move() FFI calls.
clearMinSizeTree() runs on every resize frame to suppress WebView
re-propagation. gtkSetFrame() exported as new RPC endpoint.
2026-03-25 13:16:39 +01:00
Hibryda
e6635e436c fix(electrobun): JS-based window resize replaces GTK begin_resize_drag
GTK begin_resize_drag loses grip when cursor moves inward past the 6px
handle zone. Replaced with document-level mousemove/mouseup listeners
that compute delta from initial frame and call setPosition+setSize.

- clearMinSize RPC clears WebView min-size before resize starts
- Cursor locks to resize direction during drag (body.style.cursor)
- user-select disabled during drag to prevent text selection
- Frame captured async before resize starts (no race condition)
2026-03-25 13:09:15 +01:00
Hibryda
290ae8ef86 fix(electrobun): clear min-size before each resize drag (enables shrink)
WebKitWebView re-propagates content size as minimum on every layout cycle,
overriding the one-time init fix. Now clearMinSizeTree() runs right before
each gtk_window_begin_resize_drag call, allowing resize-in (shrink).

Also removed red debug background from resize handles.
2026-03-25 13:00:11 +01:00
Hibryda
d84feb6c67 fix(electrobun): enable window resize via recursive GTK min-size override
Root cause: WebKitWebView requests min_size = content_size (5120x1387 on
ultrawide), which GTK propagates to WM_NORMAL_HINTS, blocking all resize.

Fix: recursively walk the GTK widget tree (GtkWindow → GtkBin → WebView)
and call gtk_widget_set_size_request(-1, -1) on every widget. Then set
gtk_window_set_geometry_hints with min=400x300.

Result: WM_NORMAL_HINTS now shows min=10x10, window is fully resizable.
2026-03-25 12:53:24 +01:00
Hibryda
9da9d96ebd feat(electrobun): native GTK drag/resize via gtk_window_begin_resize_drag
- gtk-window.ts: FFI wrapper calling libgtk-3.so.0 directly via bun:ffi
- begin_resize_drag: delegates resize to window manager (zero CPU, smooth)
- begin_move_drag: delegates move to window manager (replaces JS drag)
- Removed all JavaScript-based drag/resize logic (no mousemove/mouseup)
- RPC: window.beginResize + window.beginMove
- Resize handles: 4px edges + 8px corners with proper cursors
2026-03-25 02:23:24 +01:00
Hibryda
48d32f6f28 fix(electrobun): use setPosition+setSize instead of setFrame, add resize throttle 2026-03-25 02:09:54 +01:00
Hibryda
cfb7dafa17 fix(electrobun): setFrame takes positional args, not object — was causing resize jump 2026-03-25 02:02:36 +01:00
Hibryda
31338ad949 feat(electrobun): custom window chrome — no title bar, sidebar drag, edge resize
- titleBarStyle: "hidden" removes native GTK decoration
- Sidebar + right-bar: mousedown starts window drag (skips buttons)
- 8 resize handles (N/S/E/W + 4 corners) with 4px hot zones
- window.setFrame RPC for atomic position+size updates
- Min window size: 600x400
- Cursor feedback: grab on sidebars, directional resize on edges
- Frame persisted to SQLite on drag/resize end (debounced)
2026-03-25 01:52:17 +01:00
Hibryda
1de6c93e01 feat(electrobun): settings overhaul — fonts, shells, providers, retention, chords
- Settings drawer: responsive width clamp(24rem, 45vw, 50rem)
- System font detection: fc-list for UI fonts (preferred sans-serif starred)
  and mono fonts (Nerd Fonts starred), fallback to hardcoded lists
- Scrollback: default 5000, min 1000, step 500
- Shell detection: system.shells RPC, pre-selects $SHELL login shell
- Provider enablement: provider.scan gates toggle, unavailable shown as N/A
- Session retention: count 0-100 (0=Keep all), age 0-365 (0=Forever)
- Chord keybindings: Ctrl+K → Ctrl+S style multi-key sequences,
  1s prefix wait, arrow separator display, 26 tests passing
2026-03-25 01:42:34 +01:00
Hibryda
afaa2253de feat(electrobun): auto-detect Claude models via OAuth token from CLI credentials 2026-03-25 01:15:37 +01:00
Hibryda
a4d180d382 fix(electrobun): wizard fixes — native dialog, models, PathBrowser, ensureDir
- Native dialog: resolve to nearest existing parent dir, detect user cancel
  (exit code 1) vs actual error, add createIfMissing option
- Claude models: fallback to KNOWN_CLAUDE_MODELS (6 models) when API key
  unavailable. Adds Opus 4.6, Sonnet 4.6, Opus 4.5, Sonnet 4, Haiku 4.5,
  Sonnet 3.7. Live API paginated to limit=100.
- PathBrowser: Select button moved to sticky header (always visible).
  Current path shown compact in header with RTL ellipsis.
- files.ensureDir RPC: creates directory recursively before project creation
- files.ensureDir added to RPC schema
2026-03-25 01:05:15 +01:00
Hibryda
4a5e4d9733 fix(electrobun): attempt new-window-open handler for GTK false Ctrl+click
Root cause: WebKitGTK reports stale modifier state (0x14=Ctrl+Alt) after
SIGTERM of previous instance. Electrobun interprets this as Cmd+click and
opens a new window, which closes the main window.

Finding: when modifier state is clean (0x10, isCtrlHeld=0), the window
opens correctly. The event emitter API isn't publicly exported from
electrobun/bun — needs upstream fix or different approach.
2026-03-23 21:43:01 +01:00
Hibryda
f2e8b07d7f refactor(electrobun): simplify bun backend — extract db-utils, merge handlers
- db-utils.ts: shared openDb() (WAL, busy_timeout, foreign_keys, mkdirSync)
- 5 DB modules use openDb() instead of duplicated PRAGMA boilerplate
- bttask-db shares btmsg-db's Database handle (was duplicate connection)
- misc-handlers.ts: 14 inline handlers extracted from index.ts
- index.ts: 349→195 lines (only window controls remain inline)
- updater.ts: removed dead getLastKnownVersion()
- Net reduction: ~700 lines of duplicated boilerplate
2026-03-23 21:09:57 +01:00
Hibryda
e61473b025 fix(electrobun): wizard creation flow + GitLab probe + shell detection + dropdown flip
- Git probe tries GitHub then GitLab for owner/repo shorthand
- Shows "Found on GitHub/GitLab" with platform indicator
- system.shells RPC detects installed shells (bash/zsh/fish/sh/dash)
- CustomDropdown flip logic uses 200px threshold for flip-up
- Project creation properly persists all wizard fields + adds card
2026-03-23 15:34:57 +01:00
Hibryda
021feba3ed fix(electrobun): wizard 7 fixes — validation, GitLab, SSHFS, icons, model dropdown, keyboard nav
- Git Platform: validates repo via git.probe before enabling Next, supports GitHub + GitLab + any git URL
- Template dir configurable in Advanced Settings (template_dir key)
- SSHFS: checks sshfs availability, mountpoint selector when enabled
- CustomDropdown: flip-up when insufficient space below
- 50 Lucide icons (was 24) with categories (AI, Data, DevOps, Security, Media, Comms)
- Model: CustomDropdown from live API, max_tokens as slider, effort only with adaptive thinking
- Keyboard: Escape closes wizard, Tab navigation with :focus-visible rings, source cards navigable
2026-03-23 14:20:30 +01:00
Hibryda
d4014a193d feat(electrobun): project wizard phases 1-5 (WIP)
- sanitize.ts: input sanitization (trim, control chars, path traversal)
- provider-scanner.ts: detect Claude/Codex/Ollama/Gemini availability
- model-fetcher.ts: live model lists from 4 provider APIs
- ModelConfigPanel.svelte: per-provider config (thinking, effort, sandbox, temperature)
- WizardStep1-3.svelte: split wizard into composable steps
- CustomDropdown/Checkbox/Radio: themed UI components
- provider-handlers.ts: provider.scan + provider.models RPC
- Wire providers into wizard step 3 (live detection + model lists)
- Replace native selects in 5 settings panels with CustomDropdown
2026-03-23 13:05:07 +01:00
Hibryda
0d163f77e8 fix(electrobun): no double dialog on cancel, browser closes on select + max-height
- pickDirectory: removed Electrobun fallback on zenity cancel (was causing
  second dialog to appear). Cancel = return null, no fallback.
- PathBrowser: handleBrowserSelect closes browser after selection
- PathBrowser container: max-height 16rem with overflow scroll + border
2026-03-22 12:25:18 +01:00
Hibryda
d444e8aecd fix(electrobun): native folder picker via zenity — proper dark theme + folder mode
- Uses zenity --file-selection --directory for proper GTK folder chooser
  (correct "Select Project Folder" title, dirs sorted first, dark theme)
- Falls back to Electrobun Utils.openFileDialog if zenity unavailable
- RPC timeout increased to 120s (native dialogs block until user closes)
- zenity respects system GTK theme + color-scheme prefer-dark
2026-03-22 12:19:19 +01:00
Hibryda
41d5cc3c12 fix(electrobun): PathBrowser uses unguarded files.browse RPC (dirs only)
- files.browse: new RPC handler — unguarded directory listing, returns
  only directories (no file content access). Used by PathBrowser wizard.
- PathBrowser: uses files.browse instead of files.list (was blocked by
  guardPath "access denied: path outside allowed project directories")
- Home dir resolved via files.homeDir RPC (not process.env.HOME)

Note: GTK native dialog title/theme/sort controlled by Electrobun's
native wrapper — canChooseFiles:false should set SELECT_FOLDER action
but may need upstream fix for correct title.
2026-03-22 12:11:39 +01:00
Hibryda
bfc63bb595 fix(electrobun): native folder picker dialog replaces inline PathBrowser
- Uses Electrobun's Utils.openFileDialog (canChooseDirectory: true)
- files.pickDirectory + files.homeDir RPC handlers
- ProjectWizard: 📂 button opens native OS dialog
- Removed broken inline PathBrowser (process.env.HOME not in WebView)
2026-03-22 11:38:19 +01:00
Hibryda
45bca3b96f feat(electrobun): ProjectWizard — 3-step project creation with 5 source types
Step 1 — Source: local folder (path browser + validation), git clone,
GitHub URL, template (4 built-in), remote SSH
Step 2 — Configure: name, branch selector, worktree toggle, group, icon, shell
Step 3 — Agent: provider, model, permission mode, system prompt, auto-start

- ProjectWizard.svelte: 3-step wizard with display toggle (rule 55)
- PathBrowser.svelte: inline directory browser with breadcrumbs + shortcuts
- git-handlers.ts: git.branches + git.clone RPC handlers
- files.statEx RPC: path validation + git detection + writable check
- 39 new i18n keys, 172 total TranslationKey entries
- App.svelte: wizard overlay replaces simple add-project card
2026-03-22 11:17:05 +01:00
Hibryda
e75f90407b test: bun backend + store/hardening unit tests (WIP, agents running) 2026-03-22 05:02:02 +01:00
Hibryda
e73aeb4aaf test(electrobun): settings-db unit tests (partial, agents still running) 2026-03-22 04:58:37 +01:00
Hibryda
f0850f0785 feat: @agor/stores package (3 stores) + 58 BackendAdapter tests
@agor/stores:
- theme.svelte.ts, notifications.svelte.ts, health.svelte.ts extracted
- Original files replaced with re-exports (zero consumer changes needed)
- pnpm workspace + Vite/tsconfig aliases configured

BackendAdapter tests (58 new):
- backend-adapter.test.ts: 9 tests (lifecycle, singleton, testing seam)
- tauri-adapter.test.ts: 28 tests (invoke mapping, command names, params)
- electrobun-adapter.test.ts: 21 tests (RPC names, capabilities, stubs)

Total: 523 tests passing (was 465, +58)
2026-03-22 04:45:56 +01:00
Hibryda
5e1fd62ed9 feat: @agor/stores package + Electrobun hardening (WIP)
- packages/stores/: theme, notifications, health stores extracted
- Electrobun hardening: durable event sequencing, file conflict detection,
  push-based updates, backpressure guards (partial, agents still running)
2026-03-22 04:40:04 +01:00
Hibryda
0f75cb8e32 fix(electrobun): complete all 16 Codex #3 findings
CRITICAL:
- Message persistence race: snapshot batchEnd before async save
- Double-start guard: startingProjects Set prevents concurrent launches
- Symlink path traversal: fs.realpathSync() in path-guard.ts
- Relay false success: connect() returns { ok, machineId, error }

HIGH:
- Session restore skips if active session exists
- Remote remove: new RPC, cleans backend map
- Task board poll token: stale responses discarded after drag-drop
- Health concurrent tools: toolsInFlight counter (was boolean)
- bttask transactions: delete wraps comments+task, addComment validates
- PTY buffer cleared on reconnect
- PTY large paste: chunked String.fromCharCode (8KB chunks)
- Sidecar max line: 10MB limit prevents unbounded memory
- btmsg authorization: group validation, channel membership checks

MEDIUM:
- Session retention: max 5 per project, purgeSession/untrackProject
- Relay IPv6: URL parser replaces string split
- PTY schema: fixed misleading base64 comment
2026-03-22 02:52:04 +01:00
Hibryda
1cd4558740 fix(electrobun): address all 22 Codex review #2 findings
CRITICAL:
- DocsTab XSS: DOMPurify sanitization on all {@html} output
- File RPC path traversal: guardPath() validates against project CWDs

HIGH:
- SSH injection: spawn /usr/bin/ssh via PTY args, no shell string
- Search XSS: strip HTML, highlight matches client-side with <mark>
- Terminal listener leak: cleanup functions stored + called in onDestroy
- FileBrowser race: request token, discard stale responses
- SearchOverlay race: same request token pattern
- App startup ordering: groups.list chains into active_group restore
- PtyClient timeout: 5-second auth timeout on connect()
- Rule 55: 6 {#if} patterns converted to style:display toggle

MEDIUM:
- Agent persistence: only persist NEW messages (lastPersistedIndex)
- Search errors: typed error response, "Invalid query" UI
- Health store wired: agent events call recordActivity/setProjectStatus
- index.ts SRP: split into 8 domain handler modules (298 lines)
- App.svelte: extracted workspace-store.svelte.ts
- rpc.ts: typed AppRpcHandle, removed `any`

LOW:
- CommandPalette listener wired in App.svelte
- Dead code removed (removeGroup, onDragStart, plugin loaded)
2026-03-22 02:30:09 +01:00
Hibryda
8e756d3523 feat(electrobun): final 5% — full integration, real data, polish
1. Claude CLI: additionalDirectories + worktreeName passthrough
2. Agent-store: reads settings (default_cwd, provider model, permission)
3. Project hydration: SQLite replaces hardcoded PROJECTS, add/remove UI
4. Group hydration: SQLite groups, add/delete in sidebar
5. Terminal auto-spawn: reads default_cwd from settings
6. Context tab: real tokens from agent-store, file refs, turn count
7. Memory tab: Memora DB integration (read-only, graceful if missing)
8. Docs tab: markdown viewer (files.list + files.read + inline renderer)
9. SSH tab: CRUD connections, spawn PTY with ssh command
10. Error handling: global unhandledrejection → toast notifications
11. Notifications: agent done/error/stall → toasts, 15min stall timer
12. Command palette: all 18 commands (was 10)

+1,198 lines, 13 files. Electrobun now 100% feature-complete vs Tauri v3.
2026-03-22 02:02:54 +01:00
Hibryda
88206205fe feat(electrobun): multi-machine relay + OTEL telemetry
Multi-machine relay:
- relay-client.ts: WebSocket client for agor-relay with token auth,
  exponential backoff (1s-30s), TCP probe, heartbeat (15s ping)
- machines-store.svelte.ts: remote machine state tracking
- RemoteMachinesSettings.svelte: machine list, add/connect/disconnect UI
- 7 RPC types (remote.connect/disconnect/list/send/status + events)

Telemetry:
- telemetry.ts: OTEL spans + OTLP/HTTP export to Tempo,
  controlled by AGOR_OTLP_ENDPOINT env var
- telemetry-bridge.ts: tel.info/warn/error frontend convenience API
- telemetry.log RPC for frontend→Bun tracing
2026-03-22 01:46:03 +01:00