From 8d577bad9edf61b6a1fb466834452af45a93f1c8 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 08:14:29 +0700 Subject: [PATCH 01/15] fix(studio): always start SV from the exact 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..8dc2bc420 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 bind START to the current table snapshot. A previous live + // session may have cleared pendingApply, but STOP/START must never + // recycle stale waveform values from the device. + const accepted = smartSession.requestStartWithScenario( + controller.startScenarioSnapshot()) if (!accepted) { controller.pendingApply = true controller.showMessage("Start rejected. Check device status.", true) From d611ce1377085fc963b7168bc2d757dea54ac2e4 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 08:15:09 +0700 Subject: [PATCH 02/15] feat(studio): stack voltage/current full width and reclaim unused injection space --- apps/arstack_studio/qml/Main.qml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/arstack_studio/qml/Main.qml b/apps/arstack_studio/qml/Main.qml index 66a0daf89..d1b3ae26c 100644 --- a/apps/arstack_studio/qml/Main.qml +++ b/apps/arstack_studio/qml/Main.qml @@ -1049,10 +1049,10 @@ ApplicationWindow { } } - RowLayout { + ColumnLayout { Layout.fillWidth: true - Layout.preferredHeight: root.compactLayout ? 214 : 224 - Layout.maximumHeight: root.compactLayout ? 214 : 224 + Layout.fillHeight: true + Layout.minimumHeight: 350 spacing: 9 SignalMatrix { id: voltageMatrix @@ -1060,6 +1060,7 @@ ApplicationWindow { objectName: "studioVoltageMatrix" Layout.fillWidth: true Layout.fillHeight: true + Layout.minimumHeight: 164 theme: studioTheme controller: root device: device @@ -1079,6 +1080,7 @@ ApplicationWindow { objectName: "studioCurrentMatrix" Layout.fillWidth: true Layout.fillHeight: true + Layout.minimumHeight: 164 theme: studioTheme controller: root device: device @@ -1136,7 +1138,6 @@ ApplicationWindow { compact: root.compactLayout } } - Item { Layout.fillHeight: true } } } From 886363ae0be17e365142f50753efcf5f8737f986 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 08:15:47 +0700 Subject: [PATCH 03/15] fix(studio): give SMV Injector brand proper icon and 15px typography --- apps/arstack_studio/qml/ModernRibbon.qml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/arstack_studio/qml/ModernRibbon.qml b/apps/arstack_studio/qml/ModernRibbon.qml index aa995b10d..2dec9a196 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 : 178 Layout.alignment: Qt.AlignVCenter spacing: 8 Rectangle { - width: 28 - height: 28 + width: 40 + height: 40 radius: 7 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 cce5d83d2d9eb9cbadccc7bb01d8a9a91d36de43 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 08:16:02 +0700 Subject: [PATCH 04/15] fix(studio): center ribbon icon/caption consistently both axes --- apps/arstack_studio/qml/RibbonAction.qml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/arstack_studio/qml/RibbonAction.qml b/apps/arstack_studio/qml/RibbonAction.qml index a882bb495..2983b49b6 100644 --- a/apps/arstack_studio/qml/RibbonAction.qml +++ b/apps/arstack_studio/qml/RibbonAction.qml @@ -40,6 +40,9 @@ Button { : tone === "accent" || checked ? "#e1f1ff" : theme.textSoft contentItem: ColumnLayout { spacing: 3 + anchors.centerIn: parent + width: parent.width + height: implicitHeight Image { visible: control.iconSource.toString().length > 0 source: control.hovered && control.enabled ? control.hoverPath : control.iconSource @@ -56,7 +59,8 @@ Button { text: control.text color: control.textColor font: control.font - Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter elide: Text.ElideRight From e52cdf8d3564809fc872fc8413bbe1fdbb9e9d47 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 08:16:25 +0700 Subject: [PATCH 05/15] fix(studio): white neutral hover uses dark caption and centered content --- apps/arstack_studio/qml/CalmButton.qml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/arstack_studio/qml/CalmButton.qml b/apps/arstack_studio/qml/CalmButton.qml index ddddca61c..d580fd540 100644 --- a/apps/arstack_studio/qml/CalmButton.qml +++ b/apps/arstack_studio/qml/CalmButton.qml @@ -25,7 +25,7 @@ Button { tone === "success" ? (hovered ? "#1e5c43" : "#194d38") : tone === "danger" ? (hovered ? "#523039" : theme.redSoft) : tone === "accent" ? (hovered ? "#1d3c5e" : theme.accentSoft) : - (down ? "#1a2530" : hovered ? theme.raisedHover : "#141c25") + (down ? "#d6e3f1" : hovered ? "#f0f5fa" : "#141c25") readonly property color activeEdge: !enabled ? theme.lineSoft : tone === "success" ? "#347a59" : tone === "danger" ? "#86434b" : @@ -34,11 +34,13 @@ Button { readonly property color activeText: !enabled ? theme.muted2 : tone === "success" ? "#d6f4e6" : tone === "danger" ? "#ffdce0" : - tone === "accent" ? "#dcebff" : theme.textSoft + tone === "accent" ? "#dcebff" : hovered ? "#142334" : theme.textSoft contentItem: Row { spacing: control.iconSource.toString().length > 0 ? 7 : 0 anchors.centerIn: parent + width: implicitWidth + height: implicitHeight Image { visible: control.iconSource.toString().length > 0 width: control.iconSize From c203f92bbcbe9d853f505c770b6221b6ec268fd7 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 08:16:30 +0700 Subject: [PATCH 06/15] fix(studio): one stable checkbox indicator geometry on hover --- apps/arstack_studio/qml/StudioDarkCheckBox.qml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/arstack_studio/qml/StudioDarkCheckBox.qml b/apps/arstack_studio/qml/StudioDarkCheckBox.qml index eedfce586..387e8e410 100644 --- a/apps/arstack_studio/qml/StudioDarkCheckBox.qml +++ b/apps/arstack_studio/qml/StudioDarkCheckBox.qml @@ -8,6 +8,7 @@ CheckBox { property var theme property string uiFont: "Inter" implicitHeight: 30 + hoverEnabled: true spacing: 9 font.family: uiFont font.pixelSize: 11 @@ -20,7 +21,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.activeFocus || control.hovered) ? control.theme.textSoft : control.theme.line Text { anchors.centerIn: parent visible: control.checked @@ -32,6 +33,7 @@ CheckBox { } contentItem: Text { leftPadding: control.indicator.width + control.spacing + horizontalAlignment: Text.AlignLeft text: control.text font: control.font color: control.enabled ? control.theme.textSoft : control.theme.muted From fe7ad139b860577ac8a096df54f666d29ee11168 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 08:17:21 +0700 Subject: [PATCH 07/15] feat(studio): PTP SOURCE auto-start once on verified board with AUTO policy, cancellable by OFF --- apps/arstack_studio/qml/Main.qml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/arstack_studio/qml/Main.qml b/apps/arstack_studio/qml/Main.qml index d1b3ae26c..7945f18bb 100644 --- a/apps/arstack_studio/qml/Main.qml +++ b/apps/arstack_studio/qml/Main.qml @@ -118,9 +118,9 @@ ApplicationWindow { // 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 + property bool ptpDesiredForSession: true property string ptpDesiredRole: "SOURCE" - property bool ptpRestoreAfterFirmware: false + property bool ptpRestoreAfterFirmware: true property int ptpRestoreChecks: 0 property bool restoringScenario: false property bool offlinePreviewRunning: false @@ -198,6 +198,10 @@ ApplicationWindow { function onDeviceVerifiedChanged() { root.pendingApply = true root.offlinePreviewRunning = false + if (root.ptpDesiredForSession && !device.ptpRunning) { + root.ptpRestoreChecks = 0 + root.ptpRestoreAfterFirmware = true + } } } @@ -218,7 +222,7 @@ ApplicationWindow { Timer { interval: 300 repeat: true - running: root.ptpRestoreAfterFirmware + running: root.ptpRestoreAfterFirmware && device.deviceVerified onTriggered: { // Time-bounded one-shot restoration: never retry indefinitely. root.ptpRestoreChecks += 1 From 9a3ea46efbcbdfacf98920c36d48739d04504df3 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 08:17:58 +0700 Subject: [PATCH 08/15] test(studio): lock direct snapshot START, visible brand, responsive stacked layout and PTP defaults --- .../tests/check_operator_workspace.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/apps/arstack_studio/tests/check_operator_workspace.py b/apps/arstack_studio/tests/check_operator_workspace.py index ecca26e80..11f352720 100644 --- a/apps/arstack_studio/tests/check_operator_workspace.py +++ b/apps/arstack_studio/tests/check_operator_workspace.py @@ -100,6 +100,24 @@ def main() -> None: require("scenarioSnapshot()" in main_qml and "restoreLocalScenario()" in main_qml and "startScenarioSnapshot()" in main_qml and "offlinePreviewRunning" in main_qml, "offline-first scenario must persist and support one-click Start") + require('property bool ptpDesiredForSession: true' in main_qml + and 'property bool ptpRestoreAfterFirmware: true' in main_qml + and 'property bool phasorDockVisible: true' in main_qml + and 'property bool waveformDockVisible: true' in main_qml, + "operator defaults must keep PTP, phasor and waveform enabled") + require('ColumnLayout {\n Layout.fillWidth: true\n Layout.fillHeight: true\n Layout.minimumHeight: 350' in main_qml + and main_qml.index('id: voltageMatrix') < main_qml.index('id: currentMatrix'), + "full-width voltage/current table should stack vertically") + require('const accepted = smartSession.requestStartWithScenario(' in workflow + and 'smartSession.requestStart()' not in workflow.split('function requestStart() {')[1].split('function requestStop() {')[0], + "START must always transmit the exact 4I+4V model snapshot") + brand = source("ModernRibbon.qml") + require("font.pixelSize: 15" in brand and "width: 40" in brand, + "SMV Injector brand should be readable at desktop scale") + button = source("CalmButton.qml") + require('hovered ? "#f0f5fa"' in button + and 'hovered ? "#142334"' in button, + "white hover must use a dark readable caption") require("Apply to board" not in main_qml and "requestApplyDraft()" not in main_qml, "operator UI must not expose a redundant Apply-to-board action") require('saveScenarioTimer.stop()' in main_qml From a5c0f86b7e04e2a5713786b401cc5dcde98132a3 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 08:20:05 +0700 Subject: [PATCH 09/15] test(studio): match wrapped unconditional START snapshot expression --- apps/arstack_studio/tests/check_operator_workspace.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/arstack_studio/tests/check_operator_workspace.py b/apps/arstack_studio/tests/check_operator_workspace.py index 11f352720..50c09dbd8 100644 --- a/apps/arstack_studio/tests/check_operator_workspace.py +++ b/apps/arstack_studio/tests/check_operator_workspace.py @@ -134,7 +134,8 @@ 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(" in workflow + and "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 ed1cc2fa7bb4e1bf8735289788ed764919148342 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 08:21:08 +0700 Subject: [PATCH 10/15] fix(studio): eliminate native hover checkbox overlay; single custom indicator --- apps/arstack_studio/qml/StudioDarkCheckBox.qml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/arstack_studio/qml/StudioDarkCheckBox.qml b/apps/arstack_studio/qml/StudioDarkCheckBox.qml index 387e8e410..6f5e9b5b8 100644 --- a/apps/arstack_studio/qml/StudioDarkCheckBox.qml +++ b/apps/arstack_studio/qml/StudioDarkCheckBox.qml @@ -9,6 +9,9 @@ CheckBox { property string uiFont: "Inter" implicitHeight: 30 hoverEnabled: true + // Suppress native Windows hover background/second checkbox glyph. + // The single custom indicator below owns both normal and hovered states. + background: Item {} spacing: 9 font.family: uiFont font.pixelSize: 11 From d2eb3e37254e0d0d1f5ccb9bfc432918b3d405d8 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 08:23:22 +0700 Subject: [PATCH 11/15] test(studio): update second legacy START contract to unconditional multiline snapshot --- apps/arstack_studio/tests/check_operator_workspace.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/arstack_studio/tests/check_operator_workspace.py b/apps/arstack_studio/tests/check_operator_workspace.py index 50c09dbd8..c9c17b075 100644 --- a/apps/arstack_studio/tests/check_operator_workspace.py +++ b/apps/arstack_studio/tests/check_operator_workspace.py @@ -164,7 +164,8 @@ 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(' in workflow + and '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 63b0c2061899fb1e9523a36050bbc93e4b473489 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 08:31:11 +0700 Subject: [PATCH 12/15] test(studio-windows): enforce vertical voltage/current ordering and strict clipping --- 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..d6166e318 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 + // Stacked full-width 4I+4V layout: Voltage above Current; both + // must remain within the injection surface and above the PTP tray. + if (!(voltage.area.bottom() <= current.area.top() + tolerance + && current.area.bottom() <= tray.area.top() + 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 6840959c77187ac046292dfb689dfeead99ef3be Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 08:33:13 +0700 Subject: [PATCH 13/15] fix(studio): stacked 4I+4V matrices scroll safely at minimum window/DPI sizes --- apps/arstack_studio/qml/Main.qml | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/apps/arstack_studio/qml/Main.qml b/apps/arstack_studio/qml/Main.qml index 7945f18bb..a74c56b2d 100644 --- a/apps/arstack_studio/qml/Main.qml +++ b/apps/arstack_studio/qml/Main.qml @@ -1053,18 +1053,24 @@ ApplicationWindow { } } - ColumnLayout { + ScrollView { + id: signalMatrixScroll + objectName: "studioSignalMatricesScroll" Layout.fillWidth: true Layout.fillHeight: true - Layout.minimumHeight: 350 - spacing: 9 + Layout.minimumHeight: 120 + clip: true + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + ColumnLayout { + width: signalMatrixScroll.availableWidth + spacing: 9 SignalMatrix { id: voltageMatrix enabled: !workflowBar.startQueued objectName: "studioVoltageMatrix" Layout.fillWidth: true - Layout.fillHeight: true - Layout.minimumHeight: 164 + Layout.preferredHeight: root.compactLayout ? 200 : 215 + Layout.minimumHeight: 190 theme: studioTheme controller: root device: device @@ -1083,8 +1089,8 @@ ApplicationWindow { enabled: !workflowBar.startQueued objectName: "studioCurrentMatrix" Layout.fillWidth: true - Layout.fillHeight: true - Layout.minimumHeight: 164 + Layout.preferredHeight: root.compactLayout ? 200 : 215 + Layout.minimumHeight: 190 theme: studioTheme controller: root device: device @@ -1099,6 +1105,7 @@ ApplicationWindow { compact: root.compactLayout } + } } StudioTimingTray { From e18dea33c17c5871e9918ef86ad2d74928ffaf3f Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 08:33:42 +0700 Subject: [PATCH 14/15] test(studio): verify clipped scrolling 4I+4V layout at 100/150/175% DPI --- apps/arstack_studio/src/main.cpp | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/apps/arstack_studio/src/main.cpp b/apps/arstack_studio/src/main.cpp index d6166e318..bab075f69 100644 --- a/apps/arstack_studio/src/main.cpp +++ b/apps/arstack_studio/src/main.cpp @@ -65,11 +65,15 @@ struct UiBounds { const auto surface = findUiBounds(window, "studioInjectionSurface"); const auto voltage = findUiBounds(window, "studioVoltageMatrix"); const auto current = findUiBounds(window, "studioCurrentMatrix"); + const auto matrixScroll = findUiBounds(window, "studioSignalMatricesScroll"); const auto tray = findUiBounds(window, "studioTimingTray"); const auto quality = findUiBounds(window, "studioQualityRow"); - const std::array, 6> areas{{ + // The stacked matrices live in a vertical ScrollView. On 960x620 + // windows/DPI, their unscrolled content can legitimately extend below + // the viewport; only the CLIPPED scroll viewport itself must be inside. + const std::array, 5> areas{{ {"toolbar", bar}, {"injection workspace", surface}, - {"voltage matrix", voltage}, {"current matrix", current}, + {"matrix scroll viewport", matrixScroll}, {"PTP timing tray", tray}, {"quality editor", quality} }}; for (const auto& [label, item] : areas) { @@ -84,10 +88,15 @@ struct UiBounds { } // Stacked full-width 4I+4V layout: Voltage above Current; both // must remain within the injection surface and above the PTP tray. - if (!(voltage.area.bottom() <= current.area.top() + tolerance - && current.area.bottom() <= tray.area.top() + tolerance - && quality.area.top() >= tray.area.bottom() - tolerance - && quality.area.bottom() <= surface.area.bottom() + tolerance)) { + if (!(voltage.visible && current.visible && + voltage.area.width() > 100 && current.area.width() > 100 && + voltage.area.top() >= matrixScroll.area.top() - tolerance && + voltage.area.bottom() <= current.area.top() + tolerance && + current.area.left() >= matrixScroll.area.left() - tolerance && + current.area.right() <= matrixScroll.area.right() + tolerance && + matrixScroll.area.bottom() <= tray.area.top() + tolerance && + quality.area.top() >= tray.area.bottom() - tolerance && + quality.area.bottom() <= surface.area.bottom() + tolerance)) { qCritical().noquote() << "Studio UI panel order/overlap violation" << size << voltage.area << current.area << tray.area << quality.area; return false; From c86bb64e24def5138caa234cc2eb7ee31c41d732 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 08:34:00 +0700 Subject: [PATCH 15/15] test(studio): enforce compact-scrollable stacked signal matrices --- apps/arstack_studio/tests/check_operator_workspace.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/arstack_studio/tests/check_operator_workspace.py b/apps/arstack_studio/tests/check_operator_workspace.py index c9c17b075..5232ecaea 100644 --- a/apps/arstack_studio/tests/check_operator_workspace.py +++ b/apps/arstack_studio/tests/check_operator_workspace.py @@ -105,9 +105,10 @@ def main() -> None: and 'property bool phasorDockVisible: true' in main_qml and 'property bool waveformDockVisible: true' in main_qml, "operator defaults must keep PTP, phasor and waveform enabled") - require('ColumnLayout {\n Layout.fillWidth: true\n Layout.fillHeight: true\n Layout.minimumHeight: 350' in main_qml + require('objectName: "studioSignalMatricesScroll"' in main_qml + and 'ScrollBar.horizontal.policy: ScrollBar.AlwaysOff' in main_qml and main_qml.index('id: voltageMatrix') < main_qml.index('id: currentMatrix'), - "full-width voltage/current table should stack vertically") + "full-width voltage/current tables should scroll vertically when compact") require('const accepted = smartSession.requestStartWithScenario(' in workflow and 'smartSession.requestStart()' not in workflow.split('function requestStart() {')[1].split('function requestStop() {')[0], "START must always transmit the exact 4I+4V model snapshot")