"""Click bridge: design markup in, Python state out. The design is built almost entirely from ` | | delegated listener (installed once, in ) v hidden Textbox #bit-action <- value set + native `input` event dispatched | v Gradio .change() -> parse_action() -> new state -> re-render A nonce is appended to every action because Gradio's `.change()` only fires when the value actually differs; clicking the same button twice must still register. Nothing here evaluates anything. Actions are `key:value` pairs, the key must be on `ALLOWED_KEYS`, and the value is returned as an opaque string for the caller to validate against its own domain. """ from __future__ import annotations from dataclasses import dataclass # Every action the UI is allowed to emit. An unknown key is dropped, so a # hand-crafted click cannot reach code paths the UI does not offer. ALLOWED_KEYS = frozenset({ "tab", # switch a top-level tab "subtab", # switch a Compare sub-tab "acc", # toggle a left-panel accordion section "strategy", # choose a preset "asset", # choose an asset "tf", # choose a timeframe "range", # choose a date range "model", # choose a forecast model "param", # set a strategy parameter (param:key=value) "costs", # toggle costs on/off "slippage", # choose a slippage model "sizing", # choose a sizing mode "validation", # choose a validation mode "metric", # leaderboard rank metric "filter", # leaderboard filter toggle (filter:kind=value) "topn", # leaderboard row count "sigasset", # signal aggregator asset "sigtf", # signal aggregator timeframe "run", # run the backtest "example", # load the worked example "reset", # reset leaderboard filters "logscale", # toggle log scale "cvd", # toggle colorblind-safe prices "noop", # explicit no-op, used by disabled controls }) ACTION_ELEMENT_ID = "bit-action" TRIGGER_ELEMENT_ID = "bit-trigger" # Separates the action from its nonce. A pipe is used rather than a space # because action values are human-readable names -- "SMA Crossover", "Buy & Hold # (benchmark)" -- which legitimately contain spaces. No value contains a pipe. NONCE_SEP = "|" # The listener body. Installed two ways, because neither alone is reliable: # `gr.Blocks(head=...)` works locally but Hugging Face Spaces serves the page # through its own template and drops it, while `demo.load(js=...)` runs # client-side wherever the page came from. Both call the same idempotent # installer, guarded by a window flag, so running twice is harmless. BRIDGE_JS = """ """.replace("__ELEM_ID__", ACTION_ELEMENT_ID).replace( "__TRIGGER_ID__", TRIGGER_ELEMENT_ID) @dataclass(frozen=True) class Action: key: str value: str @property def is_noop(self) -> bool: return self.key == "noop" def emit(key: str, value: str = "") -> str: """Build the `data-bit` attribute value for a clickable element.""" if key not in ALLOWED_KEYS: raise ValueError(f"{key!r} is not an allowed UI action") if NONCE_SEP in str(value): raise ValueError(f"action values may not contain {NONCE_SEP!r}") return f"{key}:{value}" def parse_action(raw): """Parse what the bridge wrote into the hidden textbox. Returns None for anything unparseable or not on the allow-list, so a malformed or hand-crafted payload is ignored rather than raising. """ if not raw or not isinstance(raw, str): return None payload = raw.split(NONCE_SEP, 1)[0] if ":" not in payload: return None key, _, value = payload.partition(":") key = key.strip() if key not in ALLOWED_KEYS: return None return Action(key=key, value=value.strip()) def parse_pair(value: str): """Split a compound action value like `fast_ma=20` -> ("fast_ma", "20").""" name, _, val = value.partition("=") return name.strip(), val.strip() # `demo.load(js=...)` takes a JS *function*. This is the same installer as # BRIDGE_JS, in the form Gradio's load hook expects, and is the path that # actually works on Hugging Face Spaces. BRIDGE_LOAD_JS = """ () => { if (window.__bitBridgeInstalled) return; window.__bitBridgeInstalled = true; function holder() { var root = document.getElementById('__ELEM_ID__'); return root ? root.querySelector('textarea, input') : null; } function send(payload) { var box = holder(); if (!box) return; var nonce = Date.now().toString(36) + Math.random().toString(36).slice(2, 8); // Setting .value directly does not reliably wake Svelte's binding, so the // native setter is used and both events are dispatched... var proto = box.tagName === 'TEXTAREA' ? window.HTMLTextAreaElement.prototype : window.HTMLInputElement.prototype; var setter = Object.getOwnPropertyDescriptor(proto, 'value').set; setter.call(box, payload + '|' + nonce); box.dispatchEvent(new Event('input', { bubbles: true })); box.dispatchEvent(new Event('change', { bubbles: true })); // ...and a hidden Gradio Button is then clicked. A real click on a real // Button is the one event path Gradio always honours, so the handler fires // even if the textbox binding did not register. var trigger = document.getElementById('__TRIGGER_ID__'); var btn = trigger ? (trigger.tagName === 'BUTTON' ? trigger : trigger.querySelector('button')) : null; if (btn) { setTimeout(function () { btn.click(); }, 0); } } document.addEventListener('click', function (e) { var el = e.target && e.target.closest ? e.target.closest('[data-bit]') : null; if (!el) return; var action = el.getAttribute('data-bit'); if (!action || action.indexOf('noop:') === 0) return; e.preventDefault(); e.stopPropagation(); send(action); }, true); function commit(el) { if (!el || !el.hasAttribute || !el.hasAttribute('data-bit-input')) return; send(el.getAttribute('data-bit-input') + '=' + el.value); } document.addEventListener('change', function (e) { commit(e.target); }, true); document.addEventListener('keydown', function (e) { if (e.key === 'Enter') { commit(e.target); } }, true); } """.replace("__ELEM_ID__", ACTION_ELEMENT_ID).replace( "__TRIGGER_ID__", TRIGGER_ELEMENT_ID)