Skip to content

fix(pickers): keyboard-mode window styles hang the native file dialogs (white-screen freeze) - #73

Merged
willwade merged 2 commits into
mainfrom
fix/keyboard-mode-picker-hang
Oct 30, 2026
Merged

willwade merged 2 commits into
mainfrom
fix/keyboard-mode-picker-hang

Conversation

@willwade

@willwade willwade commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

What this fixes

v0.1.33 tester report:

"The one major issue is when you try to export/import your saved training text file, the whole program crashes, and goes into the white screen and then I have to go into task manager and end the task."

Root cause

In keyboard (direct-entry) mode the main window carries WS_EX_NOACTIVATE + WS_EX_LAYERED + TOPMOST (it floats over the target application without stealing keystrokes). A Win32 common file dialog owned by that window never surfaces: it can't take activation from a NOACTIVATE owner and is born behind the topmost one ÔÇö so IFileDialog::Show() pumps invisibly on the UI thread forever. The app stops painting (white screen) and only Task Manager can end it. Exactly the reporter's symptom.

Reproduced locally by invoking the training export from keyboard mode ÔÇö no #32770 dialog ever appears and the UI thread sits in the invisible modal pump. (Earlier "works on my machine" repros were all in editor mode, which never has these styles.)

Fix

All four picker call sites (training import/export, Open, Save) temporarily normalize the owner via the new WindowDialogStyles: clear WS_EX_NOACTIVATE, drop out of the topmost band, bring the window to the foreground, and restore both afterwards. Pure Win32 ÔÇö the Avalonia Topmost/Opacity properties are untouched so the keyboard-mode logic keeps owning them.

Verification

  • Ran the exact failing flow twice end-to-end (app restored into keyboard mode ÔåÆ Settings from the mini-bar ÔåÆ Export): the save dialog now appears within a second and the app survives the dialog being cancelled
  • 139/139 tests pass
  • Repro harnesses are watchdog-protected (app force-killed after every run)

Note: v0.1.33 predates #71 (async-void picker guards) ÔÇö both fixes must ride the next release together for the tester, along with #72 (DasherCore v0.2.27 pin, which carries the Enter/paragraph fix).


Also included (second commit): the RFC 0001 2026-10-28 amendment — ui_action + setting_changed telemetry, bringing Dasher-Windows in line with Dasher-Android and Dasher-GTK. Fixed action vocabulary declared in analytics-events.json (schema truth); string setting values are never collected; programmatic changes never emit; opt-in gated like every event. See the commit message for the full instrumentation inventory.

@greptile-apps

greptile-apps Bot commented Oct 27, 2026

Copy link
Copy Markdown

Your organization has run out of review credits. Add credits to resume code reviews.

@willwade
willwade force-pushed the fix/keyboard-mode-picker-hang branch 3 times, most recently from 8bf1e6b to 599ca56 Compare October 28, 2026 11:41
"The one major issue is when you try to export/import your saved
training text file, the whole program crashes, and goes into the
white screen and then I have to go into task manager and end the
task." (v0.1.33 tester report)

In keyboard (direct-entry) mode the main window carries
WS_EX_NOACTIVATE + WS_EX_LAYERED + TOPMOST so it floats over the
target app without stealing keystrokes. A Win32 common dialog owned
by that window never surfaces - it cannot take activation from a
NOACTIVATE owner and is born behind the topmost one - so
IFileDialog::Show pumps invisibly on the UI thread forever: the app
paints white and only Task Manager can end it. Reproduced locally
by invoking the training export from keyboard mode (no #32770 ever
appears, UI thread blocked in the modal pump); the editor-mode
repros that "worked" never had these styles.

All four picker call sites (training import/export, Open, Save) now
temporarily normalize the owner via WindowDialogStyles:
clear WS_EX_NOACTIVATE, drop out of the topmost band, bring the
window to the foreground, and restore both afterwards. Pure Win32 -
the Avalonia Topmost/Opacity properties are untouched so the mode
logic keeps owning them.

Verified twice end-to-end with the exact failing flow (app restored
into keyboard mode, Settings opened from the mini-bar, Export
clicked): the save dialog now appears within a second and the app
survives the dialog being cancelled. Repro harnesses are
watchdog-protected (the app is force-killed after every run).

Note: v0.1.33 predates #71 (async-void picker guards, crash-evidence
retention) - both that and this fix must ride the next release
together for the tester.

Signed-off-by: will wade <willwade@gmail.com>
@willwade
willwade force-pushed the fix/keyboard-mode-picker-hang branch 5 times, most recently from a80ef3a to 03614cf Compare October 30, 2026 19:02
…emetry

Implements the 2026-10-28 RFC 0001 amendment (governance
rfc0001-ui-telemetry-amendment; Android and GTK already implemented)
for Dasher-Windows, answering the UX team question "which settings
are the priority and where should main-UI focus go".

- ui_action {surface, action, state?}: main-UI control activations.
  Surfaces: main_toolbar, status_bar (incl. the bottom message-pane
  header), ime_toolbar (the keyboard-mode mini-bar). Fixed action
  vocabulary declared in analytics-events.json: new, open, save,
  settings, copy, copy_selection, cut, paste, select_all, speak,
  stop_speak, speed_up, speed_down, layout (state = pane position),
  direct_mode, game_mode, control_mode, reanchor. OnToggleMode maps
  entering (top-bar BtnKeyboard -> main_toolbar) vs leaving (mini-bar
  exit -> ime_toolbar); the BtnMode flyout items emit layout with the
  chosen position.
- setting_changed {setting_id, value_type, value?, surface}: every
  user commit at the widget site. Settings: engine parameter rows
  (toggle/slider/stepper/enum/string), the access row (access_method,
  selection_method, eye_tracker_type, udp_port, Input Filter),
  palette picker, output font family + size, keyboard opacity,
  advanced toggle, typing-rate, analytics opt-in, and the Speech
  section (speech_engine, speech_voice, speech_rate, speech_pitch,
  speech_volume, Speak All On Stop, Speak Words). Main UI: Max
  Bitrate (speed steppers), Alphabet (language picker), speech_volume
  (status bar), Automatic Speed Control / Adaptive Learning (status
  toggles - view-side, attached after the startup value load and
  suppressed around programmatic writes).

Amendment rules enforced:
- Programmatic changes never emit: the language picker compares
  against the engine alphabet before emitting (also fixes the
  pre-existing startup alphabet_selected false-fire); voice-combo and
  selection-combo auto-population are guarded; status-toggle
  instrumentation attaches view-side post-startup; sliders emit on
  drag release / keyboard adjust (commit), never per ValueChanged
  tick; steppers and the speed buttons skip clamped no-op clicks.
- String settings report the fact of the change only (free text and
  file paths are never collected); the string field skips unchanged
  focus-lost events.
- Layout actions carry the pane position in state; stop_speak is its
  own action (schema enum extended accordingly).

analytics-events.json updated (schema truth): both events, action +
state vocabulary, surface notes, and the two new
explicitly_not_collected entries from the amendment.

Signed-off-by: will wade <willwade@gmail.com>
@willwade
willwade force-pushed the fix/keyboard-mode-picker-hang branch from 03614cf to 719cdc5 Compare October 30, 2026 19:03
@willwade
willwade merged commit 671bcd0 into main Oct 30, 2026
2 checks passed
@willwade
willwade deleted the fix/keyboard-mode-picker-hang branch October 30, 2026 19:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant