From b672d790d18b75525a02791e74e5e127eddfb8c9 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 06:04:14 +0700 Subject: [PATCH 01/20] fix(studio): always apply all eight visible table setpoints before SV START --- apps/arstack_studio/qml/WorkflowBar.qml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/arstack_studio/qml/WorkflowBar.qml b/apps/arstack_studio/qml/WorkflowBar.qml index e49593114..52f427952 100644 --- a/apps/arstack_studio/qml/WorkflowBar.qml +++ b/apps/arstack_studio/qml/WorkflowBar.qml @@ -281,9 +281,12 @@ SurfacePanel { controller.showMessage(ribbon.startReason(), false) return } - const accepted = controller.pendingApply - ? smartSession.requestStartWithScenario(controller.startScenarioSnapshot()) - : smartSession.requestStart() + // Always send the live table as one bounded FIFO scenario before + // START. Dirty flags and persisted draft state are not wire readback. + // This prevents previous board phases (e.g. Ia=0) surviving when + // the table visibly specifies Ia=-30 degrees. + const accepted = smartSession.requestStartWithScenario( + controller.startScenarioSnapshot()) if (!accepted) { controller.pendingApply = true controller.showMessage("Start rejected. Check device status.", true) From 973f1f61c0c1dc21e098ecdc5d626c57238a2e64 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 06:05:25 +0700 Subject: [PATCH 02/20] style(studio): stack full-width 4V and 4I matrices to use vertical workspace --- apps/arstack_studio/qml/Main.qml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/apps/arstack_studio/qml/Main.qml b/apps/arstack_studio/qml/Main.qml index 66a0daf89..23190afca 100644 --- a/apps/arstack_studio/qml/Main.qml +++ b/apps/arstack_studio/qml/Main.qml @@ -1049,17 +1049,19 @@ ApplicationWindow { } } - RowLayout { + ColumnLayout { Layout.fillWidth: true - Layout.preferredHeight: root.compactLayout ? 214 : 224 - Layout.maximumHeight: root.compactLayout ? 214 : 224 - spacing: 9 + Layout.preferredHeight: 418 + Layout.minimumHeight: 418 + spacing: 8 SignalMatrix { id: voltageMatrix enabled: !workflowBar.startQueued objectName: "studioVoltageMatrix" Layout.fillWidth: true - Layout.fillHeight: true + Layout.preferredHeight: 205 + Layout.minimumHeight: 205 + Layout.maximumHeight: 205 theme: studioTheme controller: root device: device @@ -1078,7 +1080,9 @@ ApplicationWindow { enabled: !workflowBar.startQueued objectName: "studioCurrentMatrix" Layout.fillWidth: true - Layout.fillHeight: true + Layout.preferredHeight: 205 + Layout.minimumHeight: 205 + Layout.maximumHeight: 205 theme: studioTheme controller: root device: device From 3bd2570c04f20bfa4acbe3c651dee45b957025bd Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 06:06:24 +0700 Subject: [PATCH 03/20] style(studio): scale operator controls, align labels and unify hover contrast --- 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..b46d8ed9b 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 ? 148 : 174 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: 25 + height: 25 source: Qt.resolvedUrl("../assets/material/smv_injector.svg") - sourceSize.width: 30 - sourceSize.height: 30 + sourceSize.width: 50 + sourceSize.height: 50 } } @@ -73,7 +73,7 @@ Rectangle { text: "SMV Injector" color: bar.theme.text font.family: bar.uiFont - font.pixelSize: 11 + font.pixelSize: bar.compact ? 12 : 14 font.weight: Font.DemiBold elide: Text.ElideRight verticalAlignment: Text.AlignVCenter From c2e2f3f016dd465269af9d962463d9ef63ebbad1 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 06:06:27 +0700 Subject: [PATCH 04/20] style(studio): scale operator controls, align labels and unify hover contrast --- apps/arstack_studio/qml/ConfigurationHub.qml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/arstack_studio/qml/ConfigurationHub.qml b/apps/arstack_studio/qml/ConfigurationHub.qml index fecaa51a3..a2c44dadf 100644 --- a/apps/arstack_studio/qml/ConfigurationHub.qml +++ b/apps/arstack_studio/qml/ConfigurationHub.qml @@ -29,18 +29,21 @@ Item { component ExpertTab: TabButton { implicitHeight: 42 + hoverEnabled: true font.family: hub.uiFont font.pixelSize: 11 font.weight: checked ? Font.DemiBold : Font.Medium contentItem: Label { text: parent.text - color: parent.checked ? hub.theme.text : hub.theme.muted + color: parent.hovered && !parent.checked ? "#172536" + : 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 && !parent.checked ? "#e9f2fd" + : parent.checked ? hub.theme.raised : "transparent" radius: 6 border.width: parent.checked ? 1 : 0 border.color: hub.theme.line From 37f88c2f95e62559ff56dfe27a1987c0c1d61434 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 06:06:30 +0700 Subject: [PATCH 05/20] style(studio): scale operator controls, align labels and unify hover contrast --- apps/arstack_studio/qml/RibbonAction.qml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/arstack_studio/qml/RibbonAction.qml b/apps/arstack_studio/qml/RibbonAction.qml index a882bb495..93c3fbcd0 100644 --- a/apps/arstack_studio/qml/RibbonAction.qml +++ b/apps/arstack_studio/qml/RibbonAction.qml @@ -39,7 +39,8 @@ Button { : tone === "danger" ? "#ffe3e6" : tone === "accent" || checked ? "#e1f1ff" : theme.textSoft contentItem: ColumnLayout { - spacing: 3 + anchors.centerIn: parent + spacing: 4 Image { visible: control.iconSource.toString().length > 0 source: control.hovered && control.enabled ? control.hoverPath : control.iconSource @@ -56,7 +57,7 @@ Button { text: control.text color: control.textColor font: control.font - Layout.alignment: Qt.AlignHCenter + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter elide: Text.ElideRight From e07362456361f20eeda8b50f542afbcb594a5ffd Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 06:06:32 +0700 Subject: [PATCH 06/20] style(studio): scale operator controls, align labels and unify hover contrast --- apps/arstack_studio/qml/StudioDarkCheckBox.qml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/arstack_studio/qml/StudioDarkCheckBox.qml b/apps/arstack_studio/qml/StudioDarkCheckBox.qml index eedfce586..b08f858b4 100644 --- a/apps/arstack_studio/qml/StudioDarkCheckBox.qml +++ b/apps/arstack_studio/qml/StudioDarkCheckBox.qml @@ -9,15 +9,20 @@ CheckBox { property string uiFont: "Inter" implicitHeight: 30 spacing: 9 + hoverEnabled: true + padding: 0 font.family: uiFont font.pixelSize: 11 indicator: Rectangle { - implicitWidth: 18 - implicitHeight: 18 + implicitWidth: 19 + implicitHeight: 19 + width: 19 + height: 19 x: control.leftPadding y: (control.height - height) / 2 radius: 5 - color: control.checked ? control.theme.accent : control.theme.surface2 + color: control.checked ? control.theme.accent + : control.hovered ? control.theme.raisedHover : control.theme.surface2 border.width: 1 border.color: control.checked ? control.theme.accent : control.activeFocus ? control.theme.textSoft : control.theme.line From 00ef28b7a410d83d7f346361dc048229252a6c1b Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 06:07:18 +0700 Subject: [PATCH 07/20] feat(studio): default PTP SOURCE ON when verified, while OFF remains operator authoritative --- apps/arstack_studio/qml/Main.qml | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/apps/arstack_studio/qml/Main.qml b/apps/arstack_studio/qml/Main.qml index 23190afca..eb8d782c8 100644 --- a/apps/arstack_studio/qml/Main.qml +++ b/apps/arstack_studio/qml/Main.qml @@ -118,7 +118,8 @@ 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 bool defaultPtpStartAttempted: false property string ptpDesiredRole: "SOURCE" property bool ptpRestoreAfterFirmware: false property int ptpRestoreChecks: 0 @@ -239,6 +240,26 @@ ApplicationWindow { } } + // Operator default: PTP SOURCE ON once a verified board is ready. + // Keep the toggle tied to actual board telemetry, not this intent. + // Manual OFF cancels default ON for the rest of this application session. + Timer { + interval: 400 + repeat: true + running: !root.defaultPtpStartAttempted + onTriggered: { + const session = workflowBar.session + if (!session || !device.deviceVerified || !device.ptpAvailable || + !session.startReady || session.updatingFirmware || + device.ptpStarting) + return + root.defaultPtpStartAttempted = true + if (root.ptpDesiredForSession && !device.ptpRunning && + !root.startPtpRole("SOURCE")) + root.showMessage("PTP SOURCE could not start automatically. Use PTP On to retry.", true) + } + } + Timer { id: messageTimer interval: 2600 @@ -323,6 +344,7 @@ ApplicationWindow { } function requestStopPtp() { + root.defaultPtpStartAttempted = true root.ptpDesiredForSession = false root.ptpRestoreAfterFirmware = false return workflowBar.session && workflowBar.session.requestStopPtp() From e4ec60dbc17df4ee7f4091f398cdbf220d8cd512 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 06:07:48 +0700 Subject: [PATCH 08/20] style(studio): enforce dark readable captions on light hover for every CalmButton --- apps/arstack_studio/qml/CalmButton.qml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/arstack_studio/qml/CalmButton.qml b/apps/arstack_studio/qml/CalmButton.qml index ddddca61c..79e188fb6 100644 --- a/apps/arstack_studio/qml/CalmButton.qml +++ b/apps/arstack_studio/qml/CalmButton.qml @@ -21,17 +21,18 @@ Button { scale: down ? 0.988 : 1.0 Behavior on scale { NumberAnimation { duration: 70; easing.type: Easing.OutCubic } } - readonly property color activeFill: !enabled ? "#10161d" : - tone === "success" ? (hovered ? "#1e5c43" : "#194d38") : - tone === "danger" ? (hovered ? "#523039" : theme.redSoft) : - tone === "accent" ? (hovered ? "#1d3c5e" : theme.accentSoft) : - (down ? "#1a2530" : hovered ? theme.raisedHover : "#141c25") + readonly property color activeFill: !enabled ? "#10161d" : hovered ? "#e9f2fd" : + tone === "success" ? "#194d38" : + tone === "danger" ? theme.redSoft : + tone === "accent" ? theme.accentSoft : "#141c25" readonly property color activeEdge: !enabled ? theme.lineSoft : + hovered ? "#a6c9f2" : tone === "success" ? "#347a59" : tone === "danger" ? "#86434b" : tone === "accent" ? "#315f8d" : (activeFocus ? theme.accent : hovered ? "#3a4a5b" : theme.line) readonly property color activeText: !enabled ? theme.muted2 : + hovered ? "#142334" : tone === "success" ? "#d6f4e6" : tone === "danger" ? "#ffdce0" : tone === "accent" ? "#dcebff" : theme.textSoft From b79d330f406cd2a796c3512f1a4acb19a3df9b1e Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 06:08:30 +0700 Subject: [PATCH 09/20] style(studio): compact full-width matrix rows without clipping stacked panels --- apps/arstack_studio/qml/SignalMatrix.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/arstack_studio/qml/SignalMatrix.qml b/apps/arstack_studio/qml/SignalMatrix.qml index 1c84e2b43..0190dde10 100644 --- a/apps/arstack_studio/qml/SignalMatrix.qml +++ b/apps/arstack_studio/qml/SignalMatrix.qml @@ -196,7 +196,7 @@ Rectangle { } Layout.fillWidth: true - Layout.preferredHeight: matrix.compact ? 40 : 42 + Layout.preferredHeight: matrix.compact ? 34 : 36 color: signalRow.selected ? "#101c27" : (magnitudeField.activeFocus || phaseField.activeFocus) ? "#0f1922" : rowHover.hovered ? "#0d161e" : "transparent" From 6509e4b749fa94b77bd4d779829bc02219d2010a Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 06:09:37 +0700 Subject: [PATCH 10/20] style(studio): standardize white hover with black caption in Advanced controls --- apps/arstack_studio/qml/DarkToolButton.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/arstack_studio/qml/DarkToolButton.qml b/apps/arstack_studio/qml/DarkToolButton.qml index f21242095..6b5526990 100644 --- a/apps/arstack_studio/qml/DarkToolButton.qml +++ b/apps/arstack_studio/qml/DarkToolButton.qml @@ -29,7 +29,7 @@ ToolButton { visible: control.iconSource.toString().length === 0 anchors.fill: parent text: control.text - color: control.enabled ? (control.hovered ? "#ffffff" : theme.textSoft) : theme.muted2 + color: control.enabled ? (control.hovered ? "#142334" : theme.textSoft) : theme.muted2 font: control.font horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter @@ -37,7 +37,7 @@ ToolButton { } background: Rectangle { radius: 6 - color: control.down ? theme.accentSoft : (control.hovered ? theme.raisedHover : "transparent") + color: control.down ? theme.accentSoft : (control.hovered ? "#e9f2fd" : "transparent") border.width: control.activeFocus ? 1 : 0 border.color: theme.accent } From 229cf04d27d557825e5b55c01cab98f11ea39f47 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 06:09:40 +0700 Subject: [PATCH 11/20] style(studio): standardize white hover with black caption in Advanced controls --- apps/arstack_studio/qml/StudioDarkComboBox.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/arstack_studio/qml/StudioDarkComboBox.qml b/apps/arstack_studio/qml/StudioDarkComboBox.qml index 60f7afb00..8c9308758 100644 --- a/apps/arstack_studio/qml/StudioDarkComboBox.qml +++ b/apps/arstack_studio/qml/StudioDarkComboBox.qml @@ -54,7 +54,7 @@ ComboBox { highlighted: combo.highlightedIndex === index contentItem: Text { text: combo.textAt(choice.index) - color: combo.theme.text + color: choice.hovered || choice.highlighted ? "#142334" : combo.theme.text font: combo.font verticalAlignment: Text.AlignVCenter elide: Text.ElideRight @@ -63,7 +63,7 @@ ComboBox { } background: Rectangle { radius: 5 - color: parent.highlighted ? combo.theme.accentSoft : combo.theme.raised + color: choice.hovered || choice.highlighted ? "#e9f2fd" : combo.theme.raised } } From 420fc04efe5f8f9327dfd468b29f16fb25f4a498 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 06:10:05 +0700 Subject: [PATCH 12/20] style(studio): expand stacked voltage/current panels to fill available workspace --- apps/arstack_studio/qml/Main.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/arstack_studio/qml/Main.qml b/apps/arstack_studio/qml/Main.qml index eb8d782c8..2e91965ab 100644 --- a/apps/arstack_studio/qml/Main.qml +++ b/apps/arstack_studio/qml/Main.qml @@ -1073,7 +1073,7 @@ ApplicationWindow { ColumnLayout { Layout.fillWidth: true - Layout.preferredHeight: 418 + Layout.fillHeight: true Layout.minimumHeight: 418 spacing: 8 SignalMatrix { @@ -1081,9 +1081,9 @@ ApplicationWindow { enabled: !workflowBar.startQueued objectName: "studioVoltageMatrix" Layout.fillWidth: true + Layout.fillHeight: true Layout.preferredHeight: 205 Layout.minimumHeight: 205 - Layout.maximumHeight: 205 theme: studioTheme controller: root device: device @@ -1102,9 +1102,9 @@ ApplicationWindow { enabled: !workflowBar.startQueued objectName: "studioCurrentMatrix" Layout.fillWidth: true + Layout.fillHeight: true Layout.preferredHeight: 205 Layout.minimumHeight: 205 - Layout.maximumHeight: 205 theme: studioTheme controller: root device: device From d4a74237ddbad8c5e4d961480822162876d11078 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 06:10:36 +0700 Subject: [PATCH 13/20] test(studio): guard table-to-wire START, default PTP, stacked layout and hover consistency --- .../tests/check_operator_workspace.py | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/apps/arstack_studio/tests/check_operator_workspace.py b/apps/arstack_studio/tests/check_operator_workspace.py index ecca26e80..fd039b314 100644 --- a/apps/arstack_studio/tests/check_operator_workspace.py +++ b/apps/arstack_studio/tests/check_operator_workspace.py @@ -48,6 +48,37 @@ def main() -> None: and "root.ptpRestoreChecks > 120" in main_qml and "root.ptpDesiredForSession = false" in main_qml, "PTP user intent restoration must be bounded, explicit and cancellable") + # P0: software timing, SV egress/smpCnt and phase-angle staging must + # stay untouched by a UI-only reflow. Every START sends the actual + # eight visible channel phases, not a stale pre-existing board state. + require("const accepted = smartSession.requestStartWithScenario(" in workflow + and "controller.startScenarioSnapshot()" in workflow + and "smartSession.requestStart()" not in workflow.split( + "function requestStart() {")[1].split("function requestStop() {")[0], + "SV START must always stage the full displayed 4I+4V scenario") + require("Layout.fillHeight: true" in main_qml + and "id: voltageMatrix" in main_qml + and "id: currentMatrix" in main_qml + and "Layout.minimumHeight: 418" in main_qml, + "current/voltage matrices must use vertically stacked full-width layout") + require('property bool ptpDesiredForSession: true' in main_qml + and "defaultPtpStartAttempted" in main_qml + and "root.ptpDesiredForSession = false" in main_qml, + "PTP SOURCE default ON must preserve immediate user OFF") + ribbon = source("ModernRibbon.qml") + ribbon_action = source("RibbonAction.qml") + calm = source("CalmButton.qml") + checks = source("StudioDarkCheckBox.qml") + combo = source("StudioDarkComboBox.qml") + require("width: 38" in ribbon and "width: 25" in ribbon + and "font.pixelSize: bar.compact ? 12 : 14" in ribbon, + "SMV Injector logo/branding must remain readable at desktop scale") + require('hovered ? "#142334"' in calm + and 'hovered ? "#142334"' in ribbon_action + and '"#142334"' in combo + and "hoverEnabled: true" in checks + and "width: 19" in checks and "height: 19" in checks, + "Advanced hover captions must remain dark on white, checkbox size stable") matrix = source("SignalMatrix.qml") menu = source("StudioActionMenu.qml") cmake = (STUDIO / "CMakeLists.txt").read_text(encoding="utf-8") From 778dbdc9d1dac38865cadfbbeb732da24b9b9b4c Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 06:12:25 +0700 Subject: [PATCH 14/20] test(studio): initialize workflow source before new operator regression 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 fd039b314..8a1a005f5 100644 --- a/apps/arstack_studio/tests/check_operator_workspace.py +++ b/apps/arstack_studio/tests/check_operator_workspace.py @@ -48,6 +48,7 @@ def main() -> None: and "root.ptpRestoreChecks > 120" in main_qml and "root.ptpDesiredForSession = false" in main_qml, "PTP user intent restoration must be bounded, explicit and cancellable") + workflow = source("WorkflowBar.qml") # P0: software timing, SV egress/smpCnt and phase-angle staging must # stay untouched by a UI-only reflow. Every START sends the actual # eight visible channel phases, not a stale pre-existing board state. From 3ecddadc693c9b245445a2be9237eee36ef73503 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 06:13:54 +0700 Subject: [PATCH 15/20] test(studio): preserve scenario START guard when source formatting spans two lines --- 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 8a1a005f5..a89221dda 100644 --- a/apps/arstack_studio/tests/check_operator_workspace.py +++ b/apps/arstack_studio/tests/check_operator_workspace.py @@ -148,7 +148,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 0c685651a5510fb545769ecb8c44e6bb781dd400 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 06:15:37 +0700 Subject: [PATCH 16/20] style(studio): eliminate empty bottom spacer beneath full-height signal matrices --- apps/arstack_studio/qml/Main.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/arstack_studio/qml/Main.qml b/apps/arstack_studio/qml/Main.qml index 2e91965ab..09d7542bd 100644 --- a/apps/arstack_studio/qml/Main.qml +++ b/apps/arstack_studio/qml/Main.qml @@ -1162,7 +1162,8 @@ ApplicationWindow { compact: root.compactLayout } } - Item { Layout.fillHeight: true } + // Stacked voltage/current matrices now own any spare + // vertical space instead of an empty bottom spacer. } } From 9185228520775eef9e6bb3f66eaf9ee2a22ac614 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 06:17:21 +0700 Subject: [PATCH 17/20] test(studio): accept wrapped always-stage START call in older Edit-to-Start gate --- 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 a89221dda..b20aba788 100644 --- a/apps/arstack_studio/tests/check_operator_workspace.py +++ b/apps/arstack_studio/tests/check_operator_workspace.py @@ -178,7 +178,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 ef43c2bc71cf2232d4c46073952bce110d378aaa Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 06:26:07 +0700 Subject: [PATCH 18/20] fix(studio): scroll stacked injection workspace only when viewport height is insufficient --- apps/arstack_studio/qml/Main.qml | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/apps/arstack_studio/qml/Main.qml b/apps/arstack_studio/qml/Main.qml index 09d7542bd..7bf2dbec8 100644 --- a/apps/arstack_studio/qml/Main.qml +++ b/apps/arstack_studio/qml/Main.qml @@ -952,10 +952,27 @@ ApplicationWindow { SplitView.preferredWidth: root.compactLayout ? 700 : 860 theme: studioTheme - ColumnLayout { + Flickable { + id: injectionScroll + objectName: "studioInjectionScroll" anchors.fill: parent - anchors.margins: root.compactLayout ? 11 : 13 - spacing: root.compactLayout ? 6 : 8 + property real innerMargin: root.compactLayout ? 11 : 13 + clip: true + contentWidth: width + contentHeight: injectionContent.height + 2 * innerMargin + flickableDirection: Flickable.VerticalFlick + boundsBehavior: Flickable.StopAtBounds + interactive: contentHeight > height + ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded } + + ColumnLayout { + id: injectionContent + x: injectionScroll.innerMargin + y: injectionScroll.innerMargin + width: Math.max(0, injectionScroll.width - 2 * injectionScroll.innerMargin) + height: Math.max(injectionScroll.height - 2 * injectionScroll.innerMargin, + implicitHeight) + spacing: root.compactLayout ? 6 : 8 RowLayout { Layout.fillWidth: true @@ -1162,8 +1179,10 @@ ApplicationWindow { compact: root.compactLayout } } - // Stacked voltage/current matrices now own any spare - // vertical space instead of an empty bottom spacer. + // Stacked voltage/current matrices fill the full + // desktop panel; Flickable preserves all controls + // on smaller Windows viewports without clipping. + } } } From f36bd99d49df917a78780b82ede5aeaf271a3f88 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 06:26:42 +0700 Subject: [PATCH 19/20] test(windows): validate stacked 4V/4I scroll reachability at 960x620, 1240x760 and 1440x900 --- apps/arstack_studio/src/main.cpp | 49 +++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/apps/arstack_studio/src/main.cpp b/apps/arstack_studio/src/main.cpp index 13518af84..4fc5d7cb5 100644 --- a/apps/arstack_studio/src/main.cpp +++ b/apps/arstack_studio/src/main.cpp @@ -67,27 +67,50 @@ struct UiBounds { const auto current = findUiBounds(window, "studioCurrentMatrix"); const auto tray = findUiBounds(window, "studioTimingTray"); const auto quality = findUiBounds(window, "studioQualityRow"); - const std::array, 6> areas{{ + const auto scroll = findUiBounds(window, "studioInjectionScroll"); + const auto* scrollItem = window->findChild( + QStringLiteral("studioInjectionScroll")); + const qreal scrollContentHeight = scrollItem != nullptr + ? scrollItem->property("contentHeight").toDouble() : 0.0; + const bool needsScroll = scrollContentHeight > scroll.area.height() + tolerance; + const std::array, 7> areas{{ {"toolbar", bar}, {"injection workspace", surface}, - {"voltage matrix", voltage}, {"current matrix", current}, - {"PTP timing tray", tray}, {"quality editor", quality} + {"injection scroller", scroll}, {"voltage matrix", voltage}, + {"current matrix", current}, {"PTP timing tray", tray}, + {"quality editor", quality} }}; for (const auto& [label, item] : areas) { + const bool mustFitViewport = !needsScroll || + (item.area == bar.area || item.area == surface.area || + item.area == scroll.area); + const bool withinViewport = viewport.adjusted( + -tolerance, -tolerance, tolerance, tolerance) + .contains(item.area.adjusted(tolerance, tolerance, + -tolerance, -tolerance)); + // On minimum-height Windows screens the lower controls are reachable + // by vertical scrolling, not clipped or silently removed. + const bool insideScrollableContent = + item.area.left() >= scroll.area.left() - tolerance && + item.area.right() <= scroll.area.right() + tolerance && + item.area.top() >= scroll.area.top() - tolerance && + item.area.bottom() <= scroll.area.top() + + scrollContentHeight + tolerance; if (!item.visible || item.area.width() < 25 || item.area.height() < 15 || - !viewport.adjusted(-tolerance, -tolerance, tolerance, tolerance) - .contains(item.area.adjusted(tolerance, tolerance, - -tolerance, -tolerance))) { - qCritical().noquote() << "Studio UI viewport violation" << size << label - << "bounds=" << item.area << "viewport=" << viewport; + (mustFitViewport ? !withinViewport : !insideScrollableContent)) { + qCritical().noquote() << "Studio UI viewport/scroll violation" + << size << label << "bounds=" << item.area + << "viewport=" << viewport + << "scroll content height=" << scrollContentHeight; return false; } } - if (!(voltage.area.right() <= current.area.left() + tolerance - && tray.area.top() >= voltage.area.bottom() - tolerance + 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 - << voltage.area << current.area << tray.area << quality.area; + && quality.area.bottom() <= scroll.area.top() + scrollContentHeight + tolerance)) { + qCritical().noquote() << "Studio stacked panel order/overlap violation" + << size << voltage.area << current.area + << tray.area << quality.area; return false; } const std::array controls{{ From 2529638a758e7c0c6578a26a375c826b9c689184 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 06:27:04 +0700 Subject: [PATCH 20/20] test(studio): lock responsive stacked matrices and minimum-viewport scroll contract --- .../arstack_studio/tests/check_operator_workspace.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/arstack_studio/tests/check_operator_workspace.py b/apps/arstack_studio/tests/check_operator_workspace.py index b20aba788..4c3fe556a 100644 --- a/apps/arstack_studio/tests/check_operator_workspace.py +++ b/apps/arstack_studio/tests/check_operator_workspace.py @@ -57,11 +57,17 @@ def main() -> None: and "smartSession.requestStart()" not in workflow.split( "function requestStart() {")[1].split("function requestStop() {")[0], "SV START must always stage the full displayed 4I+4V scenario") - require("Layout.fillHeight: true" in main_qml + ui_geometry = (STUDIO / "src/main.cpp").read_text(encoding="utf-8") + require("Flickable {" in main_qml + and 'objectName: "studioInjectionScroll"' in main_qml + and "contentHeight: injectionContent.height" in main_qml + and "Layout.fillHeight: true" in main_qml and "id: voltageMatrix" in main_qml and "id: currentMatrix" in main_qml - and "Layout.minimumHeight: 418" in main_qml, - "current/voltage matrices must use vertically stacked full-width layout") + and "Layout.minimumHeight: 418" in main_qml + and "voltage.area.bottom() <= current.area.top()" in ui_geometry + and "insideScrollableContent" in ui_geometry, + "stacked 4V/4I tables must remain fully reachable at minimum Windows height") require('property bool ptpDesiredForSession: true' in main_qml and "defaultPtpStartAttempted" in main_qml and "root.ptpDesiredForSession = false" in main_qml,