From f20e673c7d96a83d6c23b6c40bbab1c06cca6efe Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 06:47:00 +0700 Subject: [PATCH 01/19] fix(studio): every START atomically queues current 4I+4V table snapshot --- apps/arstack_studio/qml/WorkflowBar.qml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/arstack_studio/qml/WorkflowBar.qml b/apps/arstack_studio/qml/WorkflowBar.qml index e49593114..5ee27ce10 100644 --- a/apps/arstack_studio/qml/WorkflowBar.qml +++ b/apps/arstack_studio/qml/WorkflowBar.qml @@ -281,9 +281,11 @@ SurfacePanel { controller.showMessage(ribbon.startReason(), false) return } - const accepted = controller.pendingApply - ? smartSession.requestStartWithScenario(controller.startScenarioSnapshot()) - : smartSession.requestStart() + // Always snapshot the eight visible table rows at START. Reusing + // the old device waveform when pendingApply is false makes GUI + // -30 deg look like transmitted 0 deg after a Stop/Start cycle. + const accepted = smartSession.requestStartWithScenario( + controller.startScenarioSnapshot()) if (!accepted) { controller.pendingApply = true controller.showMessage("Start rejected. Check device status.", true) From b25bf57cd85b1ab81b84272af06195c63233583f Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 06:47:36 +0700 Subject: [PATCH 02/19] style(studio): vertical voltage-current tables fill injection workspace --- apps/arstack_studio/qml/Main.qml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/arstack_studio/qml/Main.qml b/apps/arstack_studio/qml/Main.qml index 66a0daf89..e51f6c783 100644 --- a/apps/arstack_studio/qml/Main.qml +++ b/apps/arstack_studio/qml/Main.qml @@ -1049,17 +1049,20 @@ ApplicationWindow { } } - RowLayout { + // Stack voltage/current to use the injection workspace + // vertically instead of leaving half the panel empty. + ColumnLayout { Layout.fillWidth: true - Layout.preferredHeight: root.compactLayout ? 214 : 224 - Layout.maximumHeight: root.compactLayout ? 214 : 224 - spacing: 9 + Layout.fillHeight: true + Layout.minimumHeight: 398 + spacing: 8 SignalMatrix { id: voltageMatrix enabled: !workflowBar.startQueued objectName: "studioVoltageMatrix" Layout.fillWidth: true Layout.fillHeight: true + Layout.minimumHeight: 193 theme: studioTheme controller: root device: device @@ -1079,6 +1082,7 @@ ApplicationWindow { objectName: "studioCurrentMatrix" Layout.fillWidth: true Layout.fillHeight: true + Layout.minimumHeight: 193 theme: studioTheme controller: root device: device From 289167f2eaedd039fda135f6a348fa95975b30fd Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 06:48:05 +0700 Subject: [PATCH 03/19] style(studio): normalize branding, centered controls and accessible Advanced hover --- apps/arstack_studio/qml/ModernRibbon.qml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/arstack_studio/qml/ModernRibbon.qml b/apps/arstack_studio/qml/ModernRibbon.qml index aa995b10d..e78e13782 100644 --- a/apps/arstack_studio/qml/ModernRibbon.qml +++ b/apps/arstack_studio/qml/ModernRibbon.qml @@ -47,24 +47,24 @@ Rectangle { spacing: 6 RowLayout { - Layout.preferredWidth: bar.compact ? 105 : 124 + Layout.preferredWidth: bar.compact ? 150 : 184 Layout.alignment: Qt.AlignVCenter spacing: 8 Rectangle { - width: 28 - height: 28 - radius: 7 + width: 38 + height: 38 + radius: 9 color: "#0d151d" border.width: 1 border.color: bar.theme.lineSoft Image { anchors.centerIn: parent - width: 15 - height: 15 + width: 26 + height: 26 source: Qt.resolvedUrl("../assets/material/smv_injector.svg") - sourceSize.width: 30 - sourceSize.height: 30 + sourceSize.width: 52 + sourceSize.height: 52 } } @@ -73,7 +73,7 @@ Rectangle { text: "SMV Injector" color: bar.theme.text font.family: bar.uiFont - font.pixelSize: 11 + font.pixelSize: 15 font.weight: Font.DemiBold elide: Text.ElideRight verticalAlignment: Text.AlignVCenter From 898f62cfc6f2a2fa199112f998a6232dde116153 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 06:48:09 +0700 Subject: [PATCH 04/19] style(studio): normalize branding, centered controls and accessible Advanced hover --- apps/arstack_studio/qml/RibbonAction.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/arstack_studio/qml/RibbonAction.qml b/apps/arstack_studio/qml/RibbonAction.qml index a882bb495..e31de2cc7 100644 --- a/apps/arstack_studio/qml/RibbonAction.qml +++ b/apps/arstack_studio/qml/RibbonAction.qml @@ -39,6 +39,7 @@ Button { : tone === "danger" ? "#ffe3e6" : tone === "accent" || checked ? "#e1f1ff" : theme.textSoft contentItem: ColumnLayout { + anchors.centerIn: parent spacing: 3 Image { visible: control.iconSource.toString().length > 0 From c4d41bf671d6f2bc129b9800d8161c37ffffe18d Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 06:48:12 +0700 Subject: [PATCH 05/19] style(studio): normalize branding, centered controls and accessible Advanced hover --- apps/arstack_studio/qml/StudioDarkCheckBox.qml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/arstack_studio/qml/StudioDarkCheckBox.qml b/apps/arstack_studio/qml/StudioDarkCheckBox.qml index eedfce586..36330996a 100644 --- a/apps/arstack_studio/qml/StudioDarkCheckBox.qml +++ b/apps/arstack_studio/qml/StudioDarkCheckBox.qml @@ -8,6 +8,10 @@ CheckBox { property var theme property string uiFont: "Inter" implicitHeight: 30 + hoverEnabled: true + // The indicator is a single custom rectangle in all pointer states. + // Disable the native style focus overlay which looks like a second box. + activeFocusOnTab: true spacing: 9 font.family: uiFont font.pixelSize: 11 @@ -20,7 +24,7 @@ CheckBox { color: control.checked ? control.theme.accent : control.theme.surface2 border.width: 1 border.color: control.checked ? control.theme.accent - : control.activeFocus ? control.theme.textSoft : control.theme.line + : control.hovered || control.activeFocus ? control.theme.textSoft : control.theme.line Text { anchors.centerIn: parent visible: control.checked From 7e62cfd627f5c7f65707756c1c9c52638b873b0c Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 06:48:15 +0700 Subject: [PATCH 06/19] style(studio): normalize branding, centered controls and accessible Advanced hover --- apps/arstack_studio/qml/CalmButton.qml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/arstack_studio/qml/CalmButton.qml b/apps/arstack_studio/qml/CalmButton.qml index ddddca61c..dab85063d 100644 --- a/apps/arstack_studio/qml/CalmButton.qml +++ b/apps/arstack_studio/qml/CalmButton.qml @@ -54,6 +54,8 @@ Button { color: control.activeText font: control.font anchors.verticalCenter: parent.verticalCenter + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter Behavior on color { ColorAnimation { duration: 90 } } } } From bd4003aa945dedfe03ab8b7ca89ded1ff7a7e9ad Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 06:48:18 +0700 Subject: [PATCH 07/19] style(studio): normalize branding, centered controls and accessible Advanced hover --- apps/arstack_studio/qml/ConfigurationHub.qml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/arstack_studio/qml/ConfigurationHub.qml b/apps/arstack_studio/qml/ConfigurationHub.qml index fecaa51a3..d45c7dd5c 100644 --- a/apps/arstack_studio/qml/ConfigurationHub.qml +++ b/apps/arstack_studio/qml/ConfigurationHub.qml @@ -34,13 +34,15 @@ Item { font.weight: checked ? Font.DemiBold : Font.Medium contentItem: Label { text: parent.text - color: parent.checked ? hub.theme.text : hub.theme.muted + color: parent.hovered ? "#142334" + : parent.checked ? hub.theme.text : hub.theme.muted font: parent.font horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } background: Rectangle { - color: parent.checked ? hub.theme.raised : "transparent" + color: parent.hovered ? "#e9f2fd" + : parent.checked ? hub.theme.raised : "transparent" radius: 6 border.width: parent.checked ? 1 : 0 border.color: hub.theme.line From e772464adc7ba635881172309ae88eee9489832a Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 06:48:54 +0700 Subject: [PATCH 08/19] test(studio): lock full-table START, stacked matrices and operator UX consistency --- .../tests/check_operator_workspace.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/apps/arstack_studio/tests/check_operator_workspace.py b/apps/arstack_studio/tests/check_operator_workspace.py index ecca26e80..3a2604f25 100644 --- a/apps/arstack_studio/tests/check_operator_workspace.py +++ b/apps/arstack_studio/tests/check_operator_workspace.py @@ -54,6 +54,23 @@ def main() -> None: ordered(main_qml, "id: voltageMatrix", "id: currentMatrix", "voltage must precede current in the injection table") + require("ColumnLayout {\n Layout.fillWidth: true\n Layout.fillHeight: true\n Layout.minimumHeight: 398" in main_qml, + "voltage/current engineering tables must be vertically stacked") + workflow = source("WorkflowBar.qml") + require("smartSession.requestStartWithScenario(\n controller.startScenarioSnapshot())" in workflow + and "smartSession.requestStart()" not in workflow, + "every Start must send the current eight table rows, never stale board setpoints") + brand = source("ModernRibbon.qml") + require("font.pixelSize: 15" in brand and "width: 38" in brand, + "SMV Injector icon and title must be legible") + require('parent.hovered ? "#e9f2fd"' in config + and 'parent.hovered ? "#142334"' in config, + "Advanced tabs must retain dark text over light hover background") + require("horizontalAlignment: Text.AlignHCenter" in source("CalmButton.qml") + and "verticalAlignment: Text.AlignVCenter" in source("CalmButton.qml"), + "button captions must be centered") + require("control.hovered || control.activeFocus" in source("StudioDarkCheckBox.qml"), + "checkbox should keep a single indicator through hover") ordered(preview, "id: voltagePhasorCanvas", "id: currentPhasorCanvas", "voltage must precede current in the phasor view") ordered(preview, "drawWaveGroup(preview.voltageModel, voltageY, false)", From 278439c3d11af6cd708704fadf5f9cea03361356 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 06:51:14 +0700 Subject: [PATCH 09/19] test(studio): accept formatted full-snapshot START call in existing contract --- apps/arstack_studio/tests/check_operator_workspace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/arstack_studio/tests/check_operator_workspace.py b/apps/arstack_studio/tests/check_operator_workspace.py index 3a2604f25..fa6f6b277 100644 --- a/apps/arstack_studio/tests/check_operator_workspace.py +++ b/apps/arstack_studio/tests/check_operator_workspace.py @@ -133,7 +133,7 @@ def main() -> None: and 'enabled: !workflowBar.startQueued; onActivated: root.zeroAll()' in main_qml, "scenario editors and shortcuts must be frozen during a pending START snapshot") require("controller.pendingApply = true" in workflow - and "requestStartWithScenario(controller.startScenarioSnapshot())" in workflow + and "smartSession.requestStartWithScenario(\n controller.startScenarioSnapshot())" in workflow and "controller.applyAllSignals()" not in workflow and "startWatchdog" in workflow and "onRunningChanged()" in workflow, "reconnect must not auto-transmit, but one-click Start must stage a bounded scenario") From 6de9f25797066283f2374913b924590c708dc241 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 06:53:24 +0700 Subject: [PATCH 10/19] test(studio): update final legacy one-line Start snapshot assertion --- apps/arstack_studio/tests/check_operator_workspace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/arstack_studio/tests/check_operator_workspace.py b/apps/arstack_studio/tests/check_operator_workspace.py index fa6f6b277..7a8b2c7b7 100644 --- a/apps/arstack_studio/tests/check_operator_workspace.py +++ b/apps/arstack_studio/tests/check_operator_workspace.py @@ -162,7 +162,7 @@ def main() -> None: and 'toolTipText: "Advanced"' not in ribbon and 'ToolTip.text: "smpSynch mode"' not in timing_ui, "captioned operator controls must not duplicate labels as tooltips") - require('requestStartWithScenario(controller.startScenarioSnapshot())' in workflow + require('smartSession.requestStartWithScenario(\n controller.startScenarioSnapshot())' in workflow and "Apply to board" not in main_qml, "Edit-to-Start must not regress to a manual Apply-to-board action") require("assets/material/smv_injector.svg" in cmake_icons From c02ed997e5f1cc0a8a1040cd54395bbd0102672f Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 06:58:13 +0700 Subject: [PATCH 11/19] feat(studio): default verified-device PTP SOURCE ON, user OFF cancels in all panels --- apps/arstack_studio/qml/Main.qml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/arstack_studio/qml/Main.qml b/apps/arstack_studio/qml/Main.qml index e51f6c783..27d50ac08 100644 --- a/apps/arstack_studio/qml/Main.qml +++ b/apps/arstack_studio/qml/Main.qml @@ -115,10 +115,10 @@ ApplicationWindow { property bool phaseLink: false property bool profileDirty: false property bool pendingApply: true - // Session-only operator intent. Never persist across application launches: - // an explicit PTP ON can be restored after a firmware reflash/reconnect - // without introducing an uncontrolled power-on publisher. - property bool ptpDesiredForSession: false + // The default operator workflow enables PTP SOURCE once a verified board + // becomes READY. Nothing transmits while disconnected. Explicit PTP OFF + // cancels auto-restart for this Studio session; no setting is persisted. + property bool ptpDesiredForSession: true property string ptpDesiredRole: "SOURCE" property bool ptpRestoreAfterFirmware: false property int ptpRestoreChecks: 0 From 1d28395a2456c73954406ba823f78f445805f955 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 06:58:18 +0700 Subject: [PATCH 12/19] feat(studio): default verified-device PTP SOURCE ON, user OFF cancels in all panels --- apps/arstack_studio/qml/WorkflowBar.qml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/arstack_studio/qml/WorkflowBar.qml b/apps/arstack_studio/qml/WorkflowBar.qml index 5ee27ce10..efe8f44b1 100644 --- a/apps/arstack_studio/qml/WorkflowBar.qml +++ b/apps/arstack_studio/qml/WorkflowBar.qml @@ -150,8 +150,13 @@ SurfacePanel { // overwrite a board with a restored/offline local draft. controller.profileDirty = false controller.pendingApply = true - // Fast path: the next START uploads the local setpoints first. - // No auto-transmit on device discovery/reconnection. + // SV never starts automatically: the next START uploads the + // latest table snapshot. PTP is a separate operator-default + // timing SOURCE, armed only after verified board readiness. + if (controller.ptpDesiredForSession && !ribbon.device.ptpRunning) { + controller.ptpRestoreChecks = 0 + controller.ptpRestoreAfterFirmware = true + } } function onStateChanged() { if (ribbon.firmwareGateActive) From 1cabe36666882b9f4029c3193c6f1b5a1ba3673b Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 06:58:23 +0700 Subject: [PATCH 13/19] feat(studio): default verified-device PTP SOURCE ON, user OFF cancels in all panels --- apps/arstack_studio/qml/PtpExpertPanel.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/arstack_studio/qml/PtpExpertPanel.qml b/apps/arstack_studio/qml/PtpExpertPanel.qml index 14fb81763..35cbed9e0 100644 --- a/apps/arstack_studio/qml/PtpExpertPanel.qml +++ b/apps/arstack_studio/qml/PtpExpertPanel.qml @@ -134,7 +134,7 @@ SurfacePanel { const wantsRunning = checked checked = Qt.binding(function() { return Boolean(panel.device.ptpRunning) }) if (wantsRunning) panel.startSelectedMode() - else if (panel.session && !panel.session.requestStopPtp() && panel.controller) + else if (panel.controller && !panel.controller.requestStopPtp()) panel.controller.showMessage("PTP stop was not accepted. Copy diagnostics.", true) } } From 93f46bc927dcb13608fec78c2dc317ff962a6d7c Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 06:58:28 +0700 Subject: [PATCH 14/19] feat(studio): default verified-device PTP SOURCE ON, user OFF cancels in all panels --- apps/arstack_studio/tests/check_operator_workspace.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/arstack_studio/tests/check_operator_workspace.py b/apps/arstack_studio/tests/check_operator_workspace.py index 7a8b2c7b7..e81ba38f4 100644 --- a/apps/arstack_studio/tests/check_operator_workspace.py +++ b/apps/arstack_studio/tests/check_operator_workspace.py @@ -46,8 +46,11 @@ def main() -> None: "stale PROFILE PTPROLE must not override verified PTP runtime") require("ptpRestoreAfterFirmware" in main_qml and "root.ptpRestoreChecks > 120" in main_qml - and "root.ptpDesiredForSession = false" in main_qml, - "PTP user intent restoration must be bounded, explicit and cancellable") + and "root.ptpDesiredForSession = false" in main_qml + and "property bool ptpDesiredForSession: true" in main_qml + and "controller.ptpRestoreAfterFirmware = true" in workflow + and "panel.controller.requestStopPtp()" in expert, + "default PTP SOURCE must be verified-only and OFF must cancel it in Main and Advanced") matrix = source("SignalMatrix.qml") menu = source("StudioActionMenu.qml") cmake = (STUDIO / "CMakeLists.txt").read_text(encoding="utf-8") From 5dde0421cfe5f6e6ee2e7a5c0f7ad9546a10c9de Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 07:01:39 +0700 Subject: [PATCH 15/19] test(studio): initialize workflow contract source before early PTP default assertions --- apps/arstack_studio/tests/check_operator_workspace.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/arstack_studio/tests/check_operator_workspace.py b/apps/arstack_studio/tests/check_operator_workspace.py index e81ba38f4..292a5affd 100644 --- a/apps/arstack_studio/tests/check_operator_workspace.py +++ b/apps/arstack_studio/tests/check_operator_workspace.py @@ -26,6 +26,7 @@ def ordered(data: str, first: str, second: str, reason: str) -> None: def main() -> None: main_qml = source("Main.qml") + workflow = source("WorkflowBar.qml") preview = source("SignalPreview.qml") tray = source("StudioTimingTray.qml") expert = source("PtpExpertPanel.qml") From 7b35a2c77e6d9177238191c0f1eef68205350c23 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 07:09:38 +0700 Subject: [PATCH 16/19] test(studio): preserve Windows viewport overlap gate for stacked voltage/current matrices --- apps/arstack_studio/src/main.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/arstack_studio/src/main.cpp b/apps/arstack_studio/src/main.cpp index 13518af84..f27986097 100644 --- a/apps/arstack_studio/src/main.cpp +++ b/apps/arstack_studio/src/main.cpp @@ -82,8 +82,10 @@ struct UiBounds { return false; } } - if (!(voltage.area.right() <= current.area.left() + tolerance - && tray.area.top() >= voltage.area.bottom() - tolerance + // Voltage/current now form a vertical engineering stack. Preserve the + // strict order/overlap check at all supported Windows DPI scales. + if (!(voltage.area.bottom() <= current.area.top() + tolerance + && tray.area.top() >= current.area.bottom() - tolerance && quality.area.top() >= tray.area.bottom() - tolerance && quality.area.bottom() <= surface.area.bottom() + tolerance)) { qCritical().noquote() << "Studio UI panel order/overlap violation" << size From 590244f4b4376808561465cd6579dffca4f27311 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 07:18:15 +0700 Subject: [PATCH 17/19] fix(studio): responsive stacked signal matrices without 960x620 viewport clipping --- apps/arstack_studio/qml/Main.qml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/apps/arstack_studio/qml/Main.qml b/apps/arstack_studio/qml/Main.qml index 27d50ac08..d13d63a24 100644 --- a/apps/arstack_studio/qml/Main.qml +++ b/apps/arstack_studio/qml/Main.qml @@ -1049,13 +1049,18 @@ ApplicationWindow { } } - // Stack voltage/current to use the injection workspace - // vertically instead of leaving half the panel empty. - ColumnLayout { + // At normal desktop heights fill the workspace + // vertically. At the minimum 960x620 viewport keep + // the previous two-column arrangement: 8 full signal + // rows cannot physically fit above the PTP tray. + GridLayout { Layout.fillWidth: true - Layout.fillHeight: true - Layout.minimumHeight: 398 - spacing: 8 + Layout.fillHeight: root.height >= 735 + Layout.preferredHeight: root.height < 735 ? 214 : -1 + Layout.minimumHeight: root.height < 735 ? 193 : 398 + columns: root.height < 735 ? 2 : 1 + columnSpacing: 9 + rowSpacing: 8 SignalMatrix { id: voltageMatrix enabled: !workflowBar.startQueued From 82cdd0a91be726a229b7c1a3be19c0d369eb13a7 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 07:18:34 +0700 Subject: [PATCH 18/19] test(studio): assert stacked desktop and side-by-side compact geometry by viewport --- apps/arstack_studio/src/main.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/arstack_studio/src/main.cpp b/apps/arstack_studio/src/main.cpp index f27986097..5e2ddcb8f 100644 --- a/apps/arstack_studio/src/main.cpp +++ b/apps/arstack_studio/src/main.cpp @@ -82,10 +82,14 @@ struct UiBounds { return false; } } - // Voltage/current now form a vertical engineering stack. Preserve the - // strict order/overlap check at all supported Windows DPI scales. - if (!(voltage.area.bottom() <= current.area.top() + tolerance - && tray.area.top() >= current.area.bottom() - tolerance + // At full desktop height matrices are stacked; at the minimum viewport + // they share a row. Enforce the actual responsive geometry in both modes. + const bool matricesOrdered = size.height() >= 735 + ? voltage.area.bottom() <= current.area.top() + tolerance + : voltage.area.right() <= current.area.left() + tolerance; + const qreal matricesBottom = std::max(voltage.area.bottom(), current.area.bottom()); + if (!(matricesOrdered + && tray.area.top() >= matricesBottom - tolerance && quality.area.top() >= tray.area.bottom() - tolerance && quality.area.bottom() <= surface.area.bottom() + tolerance)) { qCritical().noquote() << "Studio UI panel order/overlap violation" << size From 98fae914c5a3df6725149d5c6a53e5dfad1b2690 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 07:18:58 +0700 Subject: [PATCH 19/19] test(studio): keep 960x620 compact geometry while stacking at normal height --- apps/arstack_studio/tests/check_operator_workspace.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/arstack_studio/tests/check_operator_workspace.py b/apps/arstack_studio/tests/check_operator_workspace.py index 292a5affd..e860f0a74 100644 --- a/apps/arstack_studio/tests/check_operator_workspace.py +++ b/apps/arstack_studio/tests/check_operator_workspace.py @@ -58,8 +58,10 @@ def main() -> None: ordered(main_qml, "id: voltageMatrix", "id: currentMatrix", "voltage must precede current in the injection table") - require("ColumnLayout {\n Layout.fillWidth: true\n Layout.fillHeight: true\n Layout.minimumHeight: 398" in main_qml, - "voltage/current engineering tables must be vertically stacked") + require("GridLayout {\n Layout.fillWidth: true" in main_qml + and "columns: root.height < 735 ? 2 : 1" in main_qml + and "Layout.minimumHeight: root.height < 735 ? 193 : 398" in main_qml, + "matrix layout must stack at normal height and fit 960x620 compact viewport") workflow = source("WorkflowBar.qml") require("smartSession.requestStartWithScenario(\n controller.startScenarioSnapshot())" in workflow and "smartSession.requestStart()" not in workflow,