diff --git a/apps/ied_simulator/qml/BrowserDataSetPane.qml b/apps/ied_simulator/qml/BrowserDataSetPane.qml index 72e70d0da..430e7252f 100644 --- a/apps/ied_simulator/qml/BrowserDataSetPane.qml +++ b/apps/ied_simulator/qml/BrowserDataSetPane.qml @@ -8,6 +8,9 @@ Rectangle { required property var theme required property var reports required property var modelProvider + // The selected DA remains owned by the existing canonical MMS model. + signal dataModelRequested() + signal watchAttributeRequested(var node) readonly property bool overview: reports.selectedDataSetIndex < 0 readonly property var selectedDataSet: !overview && reports.selectedDataSetIndex < reports.dataSets.length ? reports.dataSets[reports.selectedDataSetIndex] : ({}) color: theme.background @@ -74,6 +77,19 @@ Rectangle { root.selectedAttributeReference = "" } } + Connections { + target: root.modelProvider + function onContextChanged() { + // Context replacement may retain a textual IEC reference. Clear + // selection and lazy children so an old IED cannot drive actions + // in a new association with the same LD/LN/DO/DA names. + root.expandedMemberIndex = -1 + root.expandedMemberChildren = [] + root.showSelectedEvidence = false + root.selectedAttributeReference = "" + root.valueRevision += 1 + } + } Connections { target: root.modelProvider.treeModel function onValuesChanged() { @@ -335,17 +351,44 @@ Rectangle { spacing: 0 Rectangle { Layout.fillWidth: true - implicitHeight: 25 + implicitHeight: 30 color: theme.surfaceRaised - Label { + RowLayout { anchors.fill: parent - leftPadding: 8 - verticalAlignment: Text.AlignVCenter - text: root.selectedAttributeNode.reference - + " · " + root.displayValue(root.selectedAttributeNode.value) - color: theme.text - font.pixelSize: 9 - elide: Text.ElideMiddle + anchors.leftMargin: 8 + anchors.rightMargin: 6 + spacing: 6 + Label { + Layout.fillWidth: true + text: root.selectedAttributeNode.reference + + " · " + (root.selectedAttributeNode.hasReceivedValue === true + ? root.displayValue(root.selectedAttributeNode.value) : "NOT READ") + color: theme.text + font.pixelSize: 9 + elide: Text.ElideMiddle + ToolTip.visible: hovered + ToolTip.text: root.selectedAttributeNode.reference + } + Button { + objectName: "browserDataSetOpenInDataModel" + implicitHeight: 25 + text: "Data Model ›" + enabled: root.selectedAttributeNode.kind === "DA" + && root.selectedAttributeNode.readable === true + onClicked: root.dataModelRequested() + ToolTip.visible: hovered + ToolTip.text: "Inspect this canonical DA in Data Model; use its explicit Read command there." + } + Button { + objectName: "browserDataSetWatchSelectedDa" + implicitHeight: 25 + text: "Watch DA" + enabled: root.selectedAttributeNode.kind === "DA" + && root.selectedAttributeNode.readable === true + onClicked: root.watchAttributeRequested(root.selectedAttributeNode) + ToolTip.visible: hovered + ToolTip.text: "Add only this canonical DA to the selected IED's bounded Global Data watchlist." + } } } Repeater { diff --git a/apps/ied_simulator/qml/IedBrowserWorkspace.qml b/apps/ied_simulator/qml/IedBrowserWorkspace.qml index 226ca85b1..5630e9169 100644 --- a/apps/ied_simulator/qml/IedBrowserWorkspace.qml +++ b/apps/ied_simulator/qml/IedBrowserWorkspace.qml @@ -769,6 +769,10 @@ Item { theme: root.theme reports: root.reports modelProvider: root.context + onDataModelRequested: root.selectSection(0, "Data Model") + onWatchAttributeRequested: function(node) { + globalDataPane.addSelectedData(node) + } } BrowserReportPane { diff --git a/apps/ied_simulator/test_browser_convergence.py b/apps/ied_simulator/test_browser_convergence.py index a5eef6f65..289a8a100 100644 --- a/apps/ied_simulator/test_browser_convergence.py +++ b/apps/ied_simulator/test_browser_convergence.py @@ -87,6 +87,15 @@ 'root.showSelectedEvidence = selected', 'BrowserEngineeringEvidence {', 'objectName: "browserDataSetEvidenceInspector"', + 'objectName: "browserDataSetOpenInDataModel"', + 'objectName: "browserDataSetWatchSelectedDa"', + 'signal dataModelRequested()', + 'signal watchAttributeRequested(var node)', + 'onClicked: root.dataModelRequested()', + 'onClicked: root.watchAttributeRequested(root.selectedAttributeNode)', + 'function onContextChanged() {', + 'root.selectedAttributeReference = ""', + 'root.expandedMemberChildren = []', 'visible: root.selectedAttributeNode.kind === "DA"', 'root.selectedAttributeNode.hasReceivedValue === true', 'root.selectedAttributeNode.valueSourceReference', @@ -115,6 +124,9 @@ raise SystemExit("BROWSER_DATASET_EVIDENCE_FAIL duplicate/hidden evidence inspector") if 'Timer {' in data_set: raise SystemExit("BROWSER_DATASET_PROVENANCE_FAIL implicit per-DA timer") +if ('onDataModelRequested: root.selectSection(0, "Data Model")' not in workspace or + 'globalDataPane.addSelectedData(node)' not in workspace): + raise SystemExit("BROWSER_DATASET_HANDOFF_FAIL use canonical Data Model and per-IED watchlist") if 'readEngineeringSelected()' in data_set or 'readEngineeringSelected()' in data_model: raise SystemExit("BROWSER_TYPED_EVIDENCE_FAIL passive inspector initiated MMS Read") diff --git a/docs/WORKBENCH_CURRENT_HANDOFF.md b/docs/WORKBENCH_CURRENT_HANDOFF.md index e178448e5..88a27bbbb 100644 --- a/docs/WORKBENCH_CURRENT_HANDOFF.md +++ b/docs/WORKBENCH_CURRENT_HANDOFF.md @@ -4,6 +4,14 @@ > **Primary strategy and ownership contract:** [WORKBENCH_ARSAS_GUI_SSOT_STRATEGY.md](WORKBENCH_ARSAS_GUI_SSOT_STRATEGY.md). > **Scope:** installed native ARStack Workbench in apps/ied_simulator. Not a web app, WPF clone, or the separate ESP32-P4/SMV Studio track. +## M2M native DataSets -> Data Model handoff slice — 2026-10-15 + +- Branch `feat/workbench-m2m-dataset-da-actions-context-20261015` based on merged #179 main `ce643e81cd7640ed2b694c0914282177f871a9fa`. Keep this section IN PROGRESS until exact-head CI/merge checks. +- The selected DataSet attribute inspector gains **Data Model ›** (reuse existing canonical selection and explicit Read action) and **Watch DA** (reuse per-IED bounded Global Data addSelectedData), following ARSAS Explorer's short-path navigation without copying .NET/WPF or adding a protocol engine. +- Context replacement invalidates DataSets' expanded FCDA children and selected DA evidence even when the next IED reuses the same IEC reference. Any action is disabled without a readable selected DA. +- No extra MMS request or timer; no duplicate SCL/MMS tree. Existing stable indexed 64-child FC-matched DA projection and report-first behavior stay untouched. +- Native Qt QML smoke / 60k large model / Browser convergence and exact-head SCL-assisted gates required; selected FCDA physical evidence and standalone installed Windows UI parity remain open. + ## Active native parity + repository naming work — 2026-10-15 - Work branch: feat/native-workbench-reference-hygiene-arsas-parity-20261015; base main c8aabf292c370993950b8682718cd56b77f2cabf. IN PROGRESS until exact-head CI and merge.