XKNX / XKNX/xknxtoolkit

[Detail Bug] KNX GUI: Com Flags table and node pins disappear after parameter change switches a <choose> branch

Open
#89 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_1244263a-b1ed-4540-bf32-9804bab35c46

Introduced in 5b212db9eb994f7971076c5afb80d62d2a4d5d3e by @kewde on Jun 19, 2026

Summary

  • Context: Device (apps/knx-gui/src/knx_gui/device.py) wraps an Application and exposes its dynamic KNX com objects to the Configure panel's Com Flags table and the node editor via device.get_visible_com_objects() / device.rows.
  • Bug: Device.com_objects is captured once at construction time from the currently-visible UI tree, so com objects that a later parameter change reveals via a ComObjectParameterChoose/ChannelChoose branch switch are never added to com_objects, and get_visible_com_objects() only iterates that stale snapshot — newly-revealed com objects silently vanish from the panel.
  • Actual vs. expected: After the user edits a parameter that switches a <choose> branch, the Com Flags table and node editor should show the newly-revealed com objects; instead get_visible_com_objects() returns the intersection of the old snapshot with the new UI tree, which is empty when the branch switch replaces the whole com-object set — the Com Flags table renders "Com Flags (0)" and the node editor renders no pin rows.
  • Impact: After a parameter edit that switches a <choose> branch, com objects belonging to the newly-active branch are not in Device.com_objects, so get_visible_com_objects() drops them and the Com Flags table renders "Com Flags (0)" / the node editor renders no pin rows.

Code with Bug

apps/knx-gui/src/knx_gui/device.py

def __post_init__(self) -> None:
    if self.app.program.dynamic is not None:
        ...
        self._dynamic_ui = _DynamicUI(...)
    if not self.com_objects:
        self.com_objects = self._create_com_objects_from_app()  # <-- BUG 🔴 one-shot snapshot of currently-visible UiComObjects
def get_visible_com_objects(self) -> list[ComObject]:
    if self._cached_visible_cos is not None:
        return self._cached_visible_cos
    if self._dynamic_ui is None:
        return list(self.com_objects)
    ui_cos = _collect_ui_com_objects(self._dynamic_ui.ui())
    ui_by_id = {co.ref_id: co for co in ui_cos}
    result: list[ComObject] = []
    for co in self.com_objects:                  # <-- BUG 🔴 iterates the stale snapshot, not ui_cos
        ui_co = ui_by_id.get(co.id)
        if ui_co is None:
            continue                             # <-- BUG 🔴 a newly-revealed co is never created/appended
        co.name = ui_co.name
        ...
        result.append(co)
    self._cached_visible_cos = result
    return result
def set_param_value(self, ref_id: str, value: str) -> None:
    if self._dynamic_ui is not None:
        self._dynamic_ui.set_parameter_ref(ref_id, value)
        self._cached_visible_cos = None
        self._cached_rows = None                 # <-- BUG 🔴 com_objects is not regenerated, so the next get_visible_com_objects still filters the old snapshot

Explanation

  • DynamicUI.set_parameter_ref() invalidates the dynamic UI tree cache (self._ui = None), so the next self._dynamic_ui.ui() correctly re-evaluates the <choose> branch and returns the newly-active set of UiComObjects.
  • However, Device.com_objects is a one-time snapshot taken during Device construction from the then-active branch. After a branch switch, get_visible_com_objects() only filters and updates those stale ComObject instances; it never creates/appends ComObjects for newly-emitted UiComObjects. If the branch switch replaces the whole com-object set, all old snapshot entries are filtered out, producing [], so the Configure panel shows "Com Flags (0)" and the node editor shows no pin rows.

Codebase Inconsistency

apps/knx-gui/src/knx_gui/plugins/project/service.py intentionally updates the live Device in-place on parameter edits (no rebuild), which makes the stale snapshot observable:

def set_param(self, device: Device, param_id: str, value: str) -> None:
    if self._pid is None:
        return
    self._svc.set_parameter(self._pid, device.node_id, param_id, value)
    ...
    device.set_param_value(param_id, value)

Recommended Fix

Regenerate the com_objects snapshot inside set_param_value (after set_parameter_ref) and merge by com-object ref-id so persistent com objects keep their existing instance (and db_id) while newly-revealed com objects are created:

def set_param_value(self, ref_id: str, value: str) -> None:
    if self._dynamic_ui is not None:
        self._dynamic_ui.set_parameter_ref(ref_id, value)
        self.com_objects = self._refresh_com_objects_with_db_id_preserved()  # FIX 🟢
        self._cached_visible_cos = None
        self._cached_rows = None

History

This bug was introduced in commit 5b212db. That commit reworked Device._create_com_objects_from_app to source the com_objects snapshot from the live dynamic UI tree (self._dynamic_ui.ui(), which only emits the currently-selected <choose> branch's com objects) instead of the static self.app.com_objects() list (which carried com objects from every branch). The intent was to back Device with the project DB's module_instances/parameter_instance_refs so a rebuild faithfully reproduces the user's selected topology — but the change left set_param_value only calling self._dynamic_ui.set_parameter_ref(...) and zeroing the visibility cache, without ever regenerating self.com_objects. Combined with the in-place (no-_bump()), set_param path that the earlier commit 09f490434 had already established as the call site, the snapshot goes stale the moment a branch-switching parameter is edited: the live ui() flips to the alternate branch, but self.com_objects still holds the default-branch refs, so get_visible_com_objects's if ui_co is None: continue match drops every entry and the panel goes blank. The bug was unobservable before 5b212db because the pre-existing static-source snapshot deliberately contained com objects from every branch, so a stale snapshot still covered the newly-revealed ones.

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/device.py, reading post_init, set_param_value, get_visible_com_objects, and _create_com_objects_from_app. Trace the parameter-edit path from apps/knx-gui/src/knx_gui/plugins/project/service.py and inspect how DynamicUI.set_parameter_ref refreshes the UI tree. Done means switching a branch preserves existing database IDs and makes the newly revealed com objects appear in the Com Flags table and node editor.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
frontend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
75/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.