Sketch value fields: content-based key arbiter + the gate that can judge it

The reported defect: sketch dimension labels are "not editable" — you draw a
rectangle, its Width field opens, you type, and the as-drawn number is committed
instead. It affects every sketch tool, not just the rounded rectangle.

WHAT THIS ADDS

1. The arbiter (DesignPanel CHAR_HOOK -> DesignCanvas::inline_type_char ->
   SketchInlineEditor::type_char). Routes a key by what it IS, not by who the
   window manager focused: digits, sign, decimal separator and Backspace/Delete
   go to the open value field, Enter/Tab commit, letters stay tool shortcuts.
   This is FreeCAD Sketcher's rule (DrawSketchKeyboardManager::
   detectKeyboardEventHandlingMode), and the reason its sketcher behaves the same
   on every desktop: it never asks who has focus.

2. The [UX] trace (SNAPORCA_UXTRACE) in SketchInlineEditor: open/commit/refused/
   cancel, with the prefill and what the control actually held at Enter. It did
   not exist — the ladder below was written against a surface no build emitted,
   so it could only ever report "nothing opened". typed == prefill on a commit is
   the defect's signature and nothing else makes it visible.

3. A draw-then-edit trace in DesignSketchTool: four early returns can swallow the
   value-field chain and from outside they are indistinguishable.

4. scripts/CAD/check-gui-click-edit.py — types WITHOUT clicking the field, as a
   person does, across Line/Rectangle/Circle/Slot/Polygon/Ellipse/Arc plus label
   click-to-edit, and asserts committed == typed != prefill.

5. scripts/CAD/focus-loop.sh — sync/build/assert on behemoth. NOT the orcacad-gui
   rig: its image pins deps 216 non-CAD files behind cad-mainline, so today's CAD
   sources cannot build there without a deps rebuild.

WHAT IS PROVEN, AND WHAT IS NOT

Green under openbox: 28 checks, every tool, committed == typed != prefill.

But openbox CANNOT adjudicate this bug and the ladder says so in place. There the
field always wins the keyboard, so the same ladder also passes against a binary
with the arbiter compiled out — measured twice. Two ways of removing the keyboard
were tried and both are recorded as dead ends: XSetInputFocus loses to the field's
own re-focus CallAfter, and XSendEvent (xdotool --window) is dropped by GTK, which
made every run red regardless of the code.

Under metacity — same focus-stealing-prevention lineage as the user's mutter — the
mechanism appears in the WM's own log:

    Buggy client sent a _NET_ACTIVE_WINDOW message with a timestamp of 0

That is the activation being refused, which is exactly the reported symptom.
present_toplevel() already asks for a server timestamp, so a path is still falling
through to frame->Raise(), which sends time 0. That is the next thing to fix, and
it is tracked; the arbiter alone does not close it. metacity also aborts on this
window (frames.c:1239), so the gate needs a WM that survives before it can return
a verdict.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011FbJKJAJxxkhDTs9XdZzKA
This commit is contained in:
Tommaso Bianchi
2026-09-06 10:35:22 +02:00
co-authored by Claude Opus 5
parent af4bbe0217
commit 9134299233
9 changed files with 946 additions and 2 deletions
@@ -0,0 +1,86 @@
# DELEGATION SPECIFICATION: HARNESS-DRIVEN VALIDATION LOOP
slug: sketch-focus-arbiter · repo: /home/tommaso/projects/apps/orca_cad · branch: cad-mainline
## 1. TARGET GOAL
**Functional Objective.** Keyboard input in the Design tab is routed by WHAT THE KEY IS, not by
which widget the window manager decided to focus. Adopted from FreeCAD's
`DrawSketchKeyboardManager::detectKeyboardEventHandlingMode`
(src/Mod/Sketcher/Gui/DrawSketchKeyboardManager.cpp), which never queries focus at all:
- digit, `-`, `.`, `,` -> the open value field
- Backspace / Delete -> the open value field (when one is open)
- Enter / Return / Tab -> commit the field, control returns to the view
- a letter -> the sketch-tool shortcut map, as today
- Esc -> the existing CadLevel LIFO (DesignInteraction.hpp), unchanged
- anything else -> sticky: whoever had it keeps it
Observable postcondition: for EVERY sketch tool that opens a value field, a value typed
immediately after the field appears — with NO click into the field — is the value committed.
Today the prefill is committed instead whenever the WM withholds focus.
**Target Files / Scope (writable).**
src/slic3r/GUI/CAD/DesignPanel.cpp (the arbiter lives in the existing wxEVT_CHAR_HOOK)
src/slic3r/GUI/CAD/DesignCanvas.cpp/.hpp (forwarding entry points only)
src/slic3r/GUI/CAD/SketchInlineEditor.cpp/.hpp (accept a programmatically delivered character)
scripts/CAD/check-gui-click-edit.py (F2P oracle — authoring exception, see §4)
Everything else read-only. No dependency additions, no reformatting.
**Open Bindings.**
- The in-canvas ImGui field on wip/in-canvas-value-field is NOT in scope. Default: the arbiter
is implemented against the CURRENT wxFrame field on cad-mainline, because content-based
routing makes the window's focus irrelevant either way. If it later moves in-canvas the
arbiter is unchanged.
- Tools whose field is opened by a toolbar button rather than a gesture (Constrain path) are
covered by the same arbiter but are not in the F2P tool list. Default: assert them in P2P only.
## 2. HARNESS ENVIRONMENT & GROUND TRUTH
The rig container `orcacad-gui` on nativedev IS the harness. Xvfb `:11` + openbox, the app under
test, `xdotool` for synthetic input, and an MCP socket at `/tmp/mcp.sock` that reports sketch
state as JSON. It is a closed loop: drive input, read geometry back, assert. No window manager
politics, no human.
Harness interface (ordered; each slot one invocation, one exit code):
S1 sync docker cp <file> orcacad-gui:/OrcaSlicer/<path>
S2 build docker exec orcacad-gui ninja -C /OrcaSlicer/build orca-slicer
S3 restart docker exec orcacad-gui /OrcaSlicer/scripts/CAD/start-headless-gui.sh
S4 F2P docker exec -e DISPLAY=:11 orcacad-gui python3 /tmp/check-gui-click-edit.py --attach
S5 P2P docker exec -e DISPLAY=:11 orcacad-gui python3 /tmp/check-gui-sketching.py
**F2P.** `scripts/CAD/check-gui-click-edit.py`. For each of Line, Rectangle, Circle, Slot,
Polygon, Ellipse and Rounded rectangle: arm the tool, draw it, and type a value that differs
from the prefill WITHOUT clicking the field. Assert the committed value equals the typed value.
The ladder must FAIL against unmodified cad-mainline — that is what proves it asserts something.
**P2P.** `scripts/CAD/check-gui-sketching.py`, the existing gesture ladder, minus anything red at
baseline. NOTE: it calls `focus_field()` — one click into the field before typing — which is the
workaround this whole task removes. It stays green as a regression guard; it is NOT evidence.
**Test Integrity Constraint.** `focus_field()` in check-gui-sketching.py must NOT be deleted to
make things pass, and check-gui-click-edit.py must NOT be weakened. Either invalidates the run.
## 3. VERIFICATION COMMANDS
1. Static: `docker exec orcacad-gui ninja -C /OrcaSlicer/build orca-slicer` (warnings delta only;
this repo configures no linter — the compiler is the static gate. Absolute-zero is NOT the gate.)
2. Harness: `docker exec -e DISPLAY=:11 orcacad-gui python3 /tmp/check-gui-click-edit.py --attach`
3. Regression: `docker exec -e DISPLAY=:11 orcacad-gui python3 /tmp/check-gui-sketching.py`
## 4. CONVERGENCE LOOP — ceiling 8 iterations
EDIT (scoped) -> EXECUTE S1..S5 -> PARSE the ladder's per-tool assertions and the [UX]/[KEYTRACE]
lines -> PATCH from the parsed cause. On ceiling without convergence: stop, report the last diff
and the unresolved failure set. Do not report success.
F2P authoring exception: check-gui-click-edit.py is writable, and must be shown RED against
unmodified source before any source edit counts.
## 5. TERMINATION CRITERIA
- [ ] S2 exits 0, and introduces no compiler warning absent from the baseline.
- [ ] S4 ALL_PASSED — every tool commits the typed value, no click into the field.
- [ ] S5 shows zero regressions against its recorded baseline pass count.
- [ ] F2P proven red without the fix (source stashed, ladder re-run, must FAIL).
## 6. GUARDRAILS
Zero-assumption: no completion claim without captured stdout and exit codes. Oracle supremacy:
the ladder's verdict overrides my judgement. Blast radius: §1 files only. Baseline obligation:
run §3 once before the first edit and record it.