XKNX / XKNX/xknxtoolkit

[Detail Bug] KNX GUI: Invalid numeric input resets integer parameters to minimum and persists the corrupted value

Open Beginner friendly
#73 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
4
Forks
0
Avg merge
15h 38m
Merged PRs (30d)
37

Description

Detail Bug Report

https://app.detail.dev/org_62aa40f5-2c23-4914-a665-3bb2068af20e/bugs/bug_f50b3aae-f1a1-4189-a60c-6fc5d301e5fb

Introduced in 09f490434fc1712370be798abbd5984272ff19cd by @kewde on Jun 6, 2026

Summary

  • Context: _render_int_param in apps/knx-gui/src/knx_gui/widgets/parameter_widgets.py renders every integer parameter (both NumberWidget and NumberSliderWidget) as one-line text fields and clamps the typed value when editing finishes.
  • Bug: When the field's text can't be parsed as a plain integer, the except ValueError branch silently overwrites the parameter's bound value with min_value instead of preserving the prior value — destroying the previously-valid value and writing it back through on_change.
  • Actual vs. expected: A single non-integer character that imgui.InputTextFlags_.chars_decimal admits (., but also +, - mid-string, *, /) — e.g. typing 5.5 into a 50 valued parameter — silently resets the stored parameter to min_value (often 0) on blur; the expected behavior is to reject the unparseable input and keep the prior value.
  • Impact: Corruption of a saved project's parameter state. The wrong value is persisted through ProjectService.set_parameterEventStore.append(SetParameter(...)) to a SQLite event history that survives app reopen (per EventStore's contract: "the history is fully re-playable and survives reopen") and is re-applied live (SetParameter.apply sets param.value on the Parameter ORM row).

Code with Bug

def _render_int_param(
    widget_id: str,
    value: str,
    min_value: int | None,
    max_value: int | None,
    on_change: Callable[[str], None],
) -> None:
    _, new_text = imgui.input_text(
        f"##{widget_id}", value, imgui.InputTextFlags_.chars_decimal
    )
    if imgui.is_item_deactivated_after_edit():
        try:
            clamped = int(new_text)
        except ValueError:
            clamped = min_value if min_value is not None else 0  # <-- BUG 🔴 overwrites prior valid value with min_value on unparseable input
        if min_value is not None:
            clamped = max(min_value, clamped)
        if max_value is not None:
            clamped = min(max_value, clamped)
        if str(clamped) != value:
            on_change(str(clamped))

Explanation

  • imgui.InputTextFlags_.chars_decimal allows 0123456789.+-*/, so users can type strings like 5.5, 1+2, 3/4, etc.
  • _render_int_param parses the buffer with int(new_text), which rejects those inputs and raises ValueError.
  • The except ValueError handler replaces the value with min_value (commonly 0) and then calls on_change, so the UI change becomes a real model update.
  • That update is persisted via the Configure panel callback chain (ProjectPlugin._handle_param_changeProjectService.set_parameterEventStore.append(SetParameter(...))), and replayed on reopen (SetParameter.apply assigns param.value = self.value).

Codebase Inconsistency

  • The fallback to min_value was originally used when parsing a loaded default value feeding imgui.drag_int (which cannot produce non-integers). After commit 09f4904, the control switched to free-text input (input_text + chars_decimal) but kept the same fallback, changing its meaning from recover from malformed loaded defaults to silently overwrite user-entered invalid text with minimum.

Recommended Fix

On ValueError, do not substitute min_value; reject the unparseable input and keep the prior bound value:

    if imgui.is_item_deactivated_after_edit():
        try:
            clamped = int(new_text)
        except ValueError:
            return  # <-- FIX 🟢 reject unparseable input, keep prior bound value
        if min_value is not None:
            clamped = max(min_value, clamped)
        if max_value is not None:
            clamped = min(max_value, clamped)
        if str(clamped) != value:
            on_change(str(clamped))

History

This bug was introduced in commit 09f4904. That refactor extracted integer/time parameter rendering into _render_int_param and switched from imgui.drag_int to imgui.input_text with InputTextFlags_.chars_decimal, while keeping the old ValueError → min_value fallback. In the new free-text path, this fallback triggers on user-typed buffers that int() rejects, silently overwriting the prior value with the minimum.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in apps/knx-gui/src/knx_gui/widgets/parameter_widgets.py at _render_int_param and trace its callers for NumberWidget and NumberSliderWidget. Verify that unparseable input is rejected without changing the prior bound value or triggering on_change, while valid integers still receive the existing min/max clamping.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
desktop
Issue type
Bug
Difficulty
1/5
Estimated time
Under an hour
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
86/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.