The key tracer had been printing one letter of the answer all along

focus= in [KEYTRACE] was never a class name. GetClassName() returns const wxChar* — wchar_t* in
this build — and the line cast that to const char* and printed it with %s, so it emitted the first
byte and stopped at the padding NUL. "wxGLCanvas" came out as "w". So did "wxWindow". Every focus
reading taken from this instrument for a whole day of diagnosis was a single character, and the
one question it existed to answer — WHICH widget has the keyboard — was the one it could not
answer. Printed through wxString now, and it says focus=wxGLCanvas: the canvas does hold wx focus
while the field is open, which removes the focus hypothesis for good.

Also here, and HONESTLY LABELLED AS INCONCLUSIVE: a wxEVT_CHAR probe on the canvas. It logged
nothing (cc=0), and the tempting reading is "the characters never reach the canvas". That reading
is not available, because the probe is bound in the DesignCanvas constructor BEFORE
GLCanvas3D::bind_event_handlers(), and wx runs the most recently bound handler first —
GLCanvas3D::on_char returns without Skip() exactly when ImGui consumes a character, which is
precisely the case under test. A silent probe is therefore consistent with ImGui consuming the
keys correctly AND with them never arriving. It measures nothing. Rebind it after
bind_event_handlers(), or instrument update_key_data itself, before believing anything about it.

Writing this down rather than acting on it: I came within one commit of "fixing" a mechanism I had
inferred from an instrument that could not see it, which is the same mistake as the focus= field
above and the same mistake that cost a whole session in September.

Deployed binary restored to 00d6c191dc (md5 0eeb9a58cef5).

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:58:05 +02:00
co-authored by Claude Opus 5
parent b45d675488
commit f6c551540e
2 changed files with 19 additions and 1 deletions
+11
View File
@@ -93,6 +93,17 @@ DesignCanvas::DesignCanvas(wxWindow* parent)
// re-entrancy guard giving up looks like. Refresh() posts a paint event instead: the current
// frame finishes, the event loop runs (which is where ImGui's queued characters are consumed),
// and the next frame starts clean.
// MEASUREMENT: does a typed character reach the GL canvas at all? Everything downstream of
// this point is known good (ImGui reports want_text=1 and our InputText active), so if these
// lines do not appear the character never got past the panel's CHAR_HOOK / the focus chain,
// and no amount of work inside the field will help. Skips always: a pure observer.
if (m_canvas_widget != nullptr && std::getenv("SNAPORCA_UXTRACE")) {
m_canvas_widget->Bind(wxEVT_CHAR, [](wxKeyEvent& e) {
fprintf(stderr, "[UX] canvas_char key=%d\n", e.GetKeyCode());
fflush(stderr);
e.Skip();
});
}
m_inline_editor->request_frame = [this] {
// BOTH halves, and the dirty flag first: GLCanvas3D's paint handler returns without
// rendering when the canvas is not marked dirty, so a bare Refresh() posts an event that
+8 -1
View File
@@ -4184,7 +4184,14 @@ DesignPanel::DesignPanel(wxWindow* parent)
fprintf(stderr, "[KEYTRACE] key=%d ui_mode=%d is_sketching=%d in_text=%d inline_busy=%d focus=%s\n",
key, int(m_ui_mode), (m_viewport && m_viewport->is_sketching()) ? 1 : 0, in_text ? 1 : 0,
(m_viewport && m_viewport->inline_busy()) ? 1 : 0,
fw ? (const char*) fw->GetClassInfo()->GetClassName() : "(none)");
// wxString, not a cast: GetClassName() returns const wxChar* — wchar_t* in
// this build — and casting THAT to const char* and printing it with %s emits
// the first byte and stops at the padding NUL. Every focus= field this tracer
// has ever printed was a single letter: "wxGLCanvas" came out as "w", and so
// did "wxWindow". An instrument that silently truncates its most important
// field is worse than no instrument, and this one was trusted for a whole
// day's diagnosis.
fw ? wxString(fw->GetClassInfo()->GetClassName()).utf8_str().data() : "(none)");
fflush(stderr);
}