[eric] canvas: the host window allows visual zoom, so macOS actually delivers a trackpad pinch to the dashboard (ENG-186)

This commit is contained in:
ciregenz
2026-08-07 16:24:07 -07:00
parent b37509894d
commit 6d8ecc6fd4
3 changed files with 101 additions and 1 deletions
@@ -0,0 +1,88 @@
"""The flight envelope has to answer what/who/when/during-what from analytics alone.
The recorder was already built and wired into six error paths, but nothing asserted that what it
emits is COMPLETE. An envelope missing its lane or its breadcrumbs looks fine in a log and is useless
in an investigation, which is the exact failure it exists to prevent: every bug found by hand tonight
(a workflow that resurrected itself, a keyboard tap alive for 2h35m, an editor hung on a stalled aux
call) was silent in analytics.
These force each wired family and assert the envelope a stranger would receive.
"""
from typing import List
import pytest
from backend.apps.agents.core import flight_recorder as fr
# Straight from the clause: what a stranger needs, with no machine in front of them.
P_REQUIRED_FIELDS = {
"family", "subkind", "lane", "model", "phase", "attempts",
"breadcrumbs", "journey", "concurrency",
}
# Every family that actually attaches an envelope today (grep: build_envelope call sites).
P_WIRED_FAMILIES: List[tuple] = [
("model_error", "provider_500", "stream"),
("model_error", "auth_401", "spawn"),
("context_overflow", "overflow", "stream"),
("cli_binary_missing", "missing", "spawn"),
("context_pressure_valve", "pressure_death", "stream"),
("empty_finish", "no_answer_text", "stream"),
]
@pytest.fixture(autouse=True)
def p_clean_rings(monkeypatch):
monkeypatch.setattr(fr, "p_rings", {})
yield
@pytest.mark.parametrize("family,subkind,phase", P_WIRED_FAMILIES)
def test_every_wired_family_emits_a_complete_envelope(family, subkind, phase):
sid = f"sess-{family}"
for i in range(12):
fr.crumb(sid, "phase", step=i)
env = fr.build_envelope(sid, family, subkind, "claude-sonnet-5", phase, 3, sessions={})
missing = P_REQUIRED_FIELDS - set(env)
assert not missing, f"{family}: envelope is missing {sorted(missing)}"
assert env["family"] == family
assert env["subkind"] == subkind
assert env["phase"] == phase
assert env["attempts"] == 3
assert env["lane"], "lane must name WHICH provider the failure happened on"
def test_breadcrumbs_reach_the_ten_the_clause_asks_for():
sid = "sess-crumbs"
for i in range(15):
fr.crumb(sid, "tool_call", n=i)
env = fr.build_envelope(sid, "model_error", "x", "claude-sonnet-5", "stream", 1, sessions={})
assert len(env["breadcrumbs"]) >= 10, f"only {len(env['breadcrumbs'])} breadcrumbs"
def test_a_turn_with_no_history_still_emits_a_usable_envelope():
"""A failure at spawn has no breadcrumbs yet; the envelope must still name cause and context
rather than blowing up, or the earliest failures are exactly the ones we cannot see."""
env = fr.build_envelope("sess-empty", "cli_binary_missing", "missing", None, "spawn", 0, sessions={})
assert not (P_REQUIRED_FIELDS - set(env))
assert env["breadcrumbs"] == []
assert env["family"] == "cli_binary_missing"
def test_the_envelope_never_raises_on_a_bad_session_id():
"""It is built on the error path. If it can throw, it converts a diagnosable failure into a
second, undiagnosable one."""
for sid in ("", "does-not-exist", "\x00weird"):
env = fr.build_envelope(sid, "model_error", "x", "gpt-5", "stream", 1, sessions={})
assert env["family"] == "model_error"
def test_the_near_miss_ledger_records_a_silent_recovery():
"""The clause's last line: a recovery the user never saw still needs a denominator."""
sid = "sess-recover"
fr.crumb(sid, "api_retry", attempt=1)
fr.record_recovery(sid, net="router_respawn", model="claude-sonnet-5", attempts=2, sessions={})
labels = [c.get("l") for c in fr.breadcrumbs(sid)]
assert "recovered" in labels, f"no recovery breadcrumb, got {labels}"
+6
View File
@@ -2570,6 +2570,12 @@ app.on('web-contents-created', (_event, contents) => {
// popups are 'window' contents created with the flag OFF, so they keep the OS default.
if (isCreatingMainWindow) {
contents.on('context-menu', (_e, params) => buildAppContextMenu(contents, params));
// The dashboard IS a Figma-style canvas listening for ctrl+wheel, so it needs the very fix the
// webview branch below spells out: at the default (1,1) limits Electron drops macOS pinch instead
// of delivering it, which is the "pinch-to-zoom just stopped working" report. Widening them here
// is safe because the canvas wheel handler is passive:false and preventDefaults the zoom path, so
// Chromium never also magnifies the UI.
try { contents.setVisualZoomLevelLimits(1, 3); } catch (_) { /* older Electron */ }
}
if (contents.getType() === 'webview') {
const wcId = contents.id;
@@ -337,7 +337,13 @@ export function useCanvasControls(
// (side gutters, header) must NOT zoom/pan the hidden canvas underneath, that read as a
// glitchy zoom while scrolling the chat. Fullscreen has no canvas nav, period. The selector's
// existence check matters: a stale tile entry for a removed card would wedge the wheel forever.
if (selectFullscreenCardId(store.getState())) return;
// Swallow it rather than just ignoring it: the host window now allows visual zoom (so macOS
// delivers pinch at all), which means an un-prevented pinch here would magnify the whole UI
// instead of doing nothing.
if (selectFullscreenCardId(store.getState())) {
if (e.ctrlKey || e.metaKey) e.preventDefault();
return;
}
// Same gesture, still over the surface that owns it: let it scroll (or hit its end) natively.
if (containedEl && Date.now() - containedAt < GESTURE_GAP_MS && containedEl.isConnected
&& (e.target instanceof Node) && containedEl.contains(e.target as Node) && !(e.ctrlKey || e.metaKey)) {