From 95746c7e147a1eb6d2858307f4b2b8359a4804d2 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 12:59:17 +0700 Subject: [PATCH 01/16] fix(studio): strengthen phase table SSOT and operator workspace legibility --- apps/arstack_studio/qml/Main.qml | 35 +++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/apps/arstack_studio/qml/Main.qml b/apps/arstack_studio/qml/Main.qml index 05a29a3c6..5ec0feed2 100644 --- a/apps/arstack_studio/qml/Main.qml +++ b/apps/arstack_studio/qml/Main.qml @@ -1069,7 +1069,21 @@ ApplicationWindow { text: "3-phase link" onToggled: { root.phaseLink = checked + // Linking phases changes future edit behavior only; + // never silently rewrite existing setpoints. root.markScenarioDirty() + if (workflowBar.injectionRunning && workflowBar.session + && workflowBar.session.liveControlReady) { + // Persist *all eight current table values* on + // live link changes; the displayed draft + // must never silently diverge from TX. + var ok = true + for (var group = 0; group < 2; ++group) + for (var row = 0; row < 4; ++row) + ok = root.sendSignal(group, row) && ok + if (ok) root.pendingApply = false + else root.showMessage("Not all live phases were sent. Stop/Start to apply the table.", true) + } } font.family: root.uiFont font.pixelSize: 9 @@ -1080,21 +1094,21 @@ ApplicationWindow { ColumnLayout { Layout.fillWidth: true - // Stack Voltage and Current vertically; allocate - // the available height between the two matrices. - Layout.fillHeight: true - Layout.preferredHeight: root.compactLayout ? 350 : 450 - // Allow 960x620 minimum viewport to shrink without - // clipping timing and quality rows; on larger - // viewports this column expands into spare space. - Layout.minimumHeight: root.compactLayout ? 180 : 210 + // Two stacked tables consume their actual content + // heights, never stretch four tiny rows into an + // oversized empty card at tall window sizes. + Layout.fillHeight: false + Layout.preferredHeight: root.compactLayout ? 378 : 410 + Layout.maximumHeight: 420 + Layout.minimumHeight: root.compactLayout ? 340 : 370 spacing: 8 SignalMatrix { id: voltageMatrix enabled: !workflowBar.startQueued objectName: "studioVoltageMatrix" Layout.fillWidth: true - Layout.fillHeight: true + Layout.fillHeight: false + Layout.preferredHeight: root.compactLayout ? 181 : 196 theme: studioTheme controller: root device: device @@ -1113,7 +1127,8 @@ ApplicationWindow { enabled: !workflowBar.startQueued objectName: "studioCurrentMatrix" Layout.fillWidth: true - Layout.fillHeight: true + Layout.fillHeight: false + Layout.preferredHeight: root.compactLayout ? 181 : 196 theme: studioTheme controller: root device: device From 8f900ddde9327175030e44f665a143a05f5b2116 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 12:59:21 +0700 Subject: [PATCH 02/16] fix(studio): strengthen phase table SSOT and operator workspace legibility --- 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 bf9a737ab..22f18779c 100644 --- a/apps/arstack_studio/qml/ModernRibbon.qml +++ b/apps/arstack_studio/qml/ModernRibbon.qml @@ -47,23 +47,23 @@ Rectangle { spacing: 6 RowLayout { - Layout.preferredWidth: bar.compact ? 220 : 270 + Layout.preferredWidth: bar.compact ? 232 : 286 Layout.alignment: Qt.AlignVCenter spacing: 8 Rectangle { - Layout.preferredWidth: 56 - Layout.preferredHeight: 56 - width: 56 - height: 56 + Layout.preferredWidth: 64 + Layout.preferredHeight: 64 + width: 64 + height: 64 radius: 10 color: "#0d151d" border.width: 1 border.color: bar.theme.lineSoft Image { anchors.centerIn: parent - width: 44 - height: 44 + width: 53 + height: 53 source: Qt.resolvedUrl("../assets/material/smv_injector.svg") sourceSize.width: 68 sourceSize.height: 68 @@ -75,7 +75,7 @@ Rectangle { text: "SMV Injector" color: bar.theme.text font.family: bar.uiFont - font.pixelSize: bar.compact ? 20 : 22 + font.pixelSize: bar.compact ? 21 : 25 font.weight: Font.Bold elide: Text.ElideRight verticalAlignment: Text.AlignVCenter From ca0f4be788fe34b2612577c5c81f7b4327b943e2 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 12:59:24 +0700 Subject: [PATCH 03/16] fix(studio): strengthen phase table SSOT and operator workspace legibility --- 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 8fd0d320a..c2ff28181 100644 --- a/apps/arstack_studio/qml/StudioDarkCheckBox.qml +++ b/apps/arstack_studio/qml/StudioDarkCheckBox.qml @@ -8,6 +8,9 @@ CheckBox { property var theme property string uiFont: "Inter" implicitHeight: 32 + // The same single indicator is painted for normal, hover and focus. + // Do not overlay a native checkbox glyph on hover. + display: AbstractButton.IconOnly // Keep one fixed indicator geometry at rest, during hover and focus. // Native style decorations must not replace this explicit indicator. background: Item { implicitWidth: 1; implicitHeight: 1 } From 4bff585cee2da0206a9d7de7d67cabf4882cdda7 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 12:59:28 +0700 Subject: [PATCH 04/16] fix(studio): strengthen phase table SSOT and operator workspace legibility --- apps/arstack_studio/qml/CalmButton.qml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/arstack_studio/qml/CalmButton.qml b/apps/arstack_studio/qml/CalmButton.qml index 924570197..53e744f2d 100644 --- a/apps/arstack_studio/qml/CalmButton.qml +++ b/apps/arstack_studio/qml/CalmButton.qml @@ -41,9 +41,12 @@ Button { tone === "accent" ? "#dcebff" : theme.textSoft contentItem: Row { - // Center the complete icon+caption pair in both axes in Qt Controls. - anchors.centerIn: parent + // Controls own content geometry; use its assigned rect instead of + // anchoring inside a managed contentItem (invalid on some Qt styles). + anchors.fill: parent spacing: control.iconSource.toString().length > 0 ? 7 : 0 + // Row is sized to the center, children remain vertically centered. + leftPadding: Math.max(0, (width - implicitWidth) / 2) Image { visible: control.iconSource.toString().length > 0 width: control.iconSize From 51b071612a607a7259668698451e0a4f1a9c95e9 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 12:59:33 +0700 Subject: [PATCH 05/16] fix(studio): strengthen phase table SSOT and operator workspace legibility --- .../tests/check_operator_workspace.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/apps/arstack_studio/tests/check_operator_workspace.py b/apps/arstack_studio/tests/check_operator_workspace.py index 11825422f..13fc6760d 100644 --- a/apps/arstack_studio/tests/check_operator_workspace.py +++ b/apps/arstack_studio/tests/check_operator_workspace.py @@ -76,6 +76,19 @@ 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") + ribbon = source("ModernRibbon.qml") + checkbox = source("StudioDarkCheckBox.qml") + calm = source("CalmButton.qml") + require("Layout.preferredWidth: bar.compact ? 232 : 286" in ribbon + and "font.pixelSize: bar.compact ? 21 : 25" in ribbon, + "operator brand/logo must stay legible") + require("display: AbstractButton.IconOnly" in checkbox, + "checkbox must keep one stable custom indicator on hover") + require("leftPadding: Math.max(0, (width - implicitWidth) / 2)" in calm, + "button caption needs horizontal centering") + require("root.sendSignal(group, row)" in main_qml + and "Layout.fillHeight: false" in main_qml, + "table and sent phases must remain consistent") matrix = source("SignalMatrix.qml") require("Layout.fillHeight: true" in matrix and "Layout.minimumHeight: 34" in matrix From 3995051082666d001274ada5c40173a113238946 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 12:59:59 +0700 Subject: [PATCH 06/16] fix(qml): center button row in valid Item content root --- apps/arstack_studio/qml/CalmButton.qml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/apps/arstack_studio/qml/CalmButton.qml b/apps/arstack_studio/qml/CalmButton.qml index 53e744f2d..66d0dd2b2 100644 --- a/apps/arstack_studio/qml/CalmButton.qml +++ b/apps/arstack_studio/qml/CalmButton.qml @@ -40,13 +40,13 @@ Button { tone === "danger" ? "#ffdce0" : tone === "accent" ? "#dcebff" : theme.textSoft - contentItem: Row { - // Controls own content geometry; use its assigned rect instead of - // anchoring inside a managed contentItem (invalid on some Qt styles). - anchors.fill: parent - spacing: control.iconSource.toString().length > 0 ? 7 : 0 - // Row is sized to the center, children remain vertically centered. - leftPadding: Math.max(0, (width - implicitWidth) / 2) + contentItem: Item { + implicitWidth: centeredContent.implicitWidth + implicitHeight: centeredContent.implicitHeight + Row { + id: centeredContent + anchors.centerIn: parent + spacing: control.iconSource.toString().length > 0 ? 7 : 0 Image { visible: control.iconSource.toString().length > 0 width: control.iconSize @@ -64,6 +64,7 @@ Button { anchors.verticalCenter: parent.verticalCenter Behavior on color { ColorAnimation { duration: 90 } } } + } } ToolTip.visible: hovered && toolTipText.length > 0 From 87452b30e842089c769484b5ed8828d51e53e378 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 13:00:05 +0700 Subject: [PATCH 07/16] fix(qml): keep checkbox label visible with single custom indicator --- apps/arstack_studio/qml/StudioDarkCheckBox.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/arstack_studio/qml/StudioDarkCheckBox.qml b/apps/arstack_studio/qml/StudioDarkCheckBox.qml index c2ff28181..c47b6d679 100644 --- a/apps/arstack_studio/qml/StudioDarkCheckBox.qml +++ b/apps/arstack_studio/qml/StudioDarkCheckBox.qml @@ -10,7 +10,7 @@ CheckBox { implicitHeight: 32 // The same single indicator is painted for normal, hover and focus. // Do not overlay a native checkbox glyph on hover. - display: AbstractButton.IconOnly + // Only the custom Rectangle paints the indicator; native style stays hidden. // Keep one fixed indicator geometry at rest, during hover and focus. // Native style decorations must not replace this explicit indicator. background: Item { implicitWidth: 1; implicitHeight: 1 } From a34492bd6e7b726d6038524b2688a18b56dfde97 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 13:00:11 +0700 Subject: [PATCH 08/16] test(studio): enforce valid centered controls and one checkbox indicator --- apps/arstack_studio/tests/check_operator_workspace.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/arstack_studio/tests/check_operator_workspace.py b/apps/arstack_studio/tests/check_operator_workspace.py index 13fc6760d..fad605d99 100644 --- a/apps/arstack_studio/tests/check_operator_workspace.py +++ b/apps/arstack_studio/tests/check_operator_workspace.py @@ -82,9 +82,9 @@ def main() -> None: require("Layout.preferredWidth: bar.compact ? 232 : 286" in ribbon and "font.pixelSize: bar.compact ? 21 : 25" in ribbon, "operator brand/logo must stay legible") - require("display: AbstractButton.IconOnly" in checkbox, + require("indicator: Rectangle {" in checkbox, "checkbox must keep one stable custom indicator on hover") - require("leftPadding: Math.max(0, (width - implicitWidth) / 2)" in calm, + require("anchors.centerIn: parent" in calm, "button caption needs horizontal centering") require("root.sendSignal(group, row)" in main_qml and "Layout.fillHeight: false" in main_qml, From 7a9a00ea82edec30a02ba58c0c96562cc5648205 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 13:01:04 +0700 Subject: [PATCH 09/16] feat(studio): default verified-session PTP SOURCE ON without extra dialogs --- apps/arstack_studio/qml/Main.qml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/apps/arstack_studio/qml/Main.qml b/apps/arstack_studio/qml/Main.qml index 5ec0feed2..dbd92b6f0 100644 --- a/apps/arstack_studio/qml/Main.qml +++ b/apps/arstack_studio/qml/Main.qml @@ -118,6 +118,10 @@ ApplicationWindow { // an explicit PTP ON can be restored after a firmware reflash/reconnect // without introducing an uncontrolled power-on publisher. property bool ptpDesiredForSession: false + // Default PTP SOURCE is a one-shot session convenience: only after + // verified firmware + a ready profile, never during recovery/flashing. + // An explicit PTP OFF cancels automatic rearming. + property bool ptpDefaultPending: true property string ptpDesiredRole: "SOURCE" property bool ptpRestoreAfterFirmware: false property int ptpRestoreChecks: 0 @@ -256,6 +260,24 @@ ApplicationWindow { } } + Timer { + interval: 350 + repeat: true + running: root.ptpDefaultPending + onTriggered: { + const session = workflowBar.session + if (!session || !device.deviceVerified || !session.startReady || + session.updatingFirmware || device.ptpStarting) + return + root.ptpDefaultPending = false + if (!device.ptpRunning) { + // Use the same configured, verified PTP START as the switch. + if (!root.startPtpRole("SOURCE")) + root.showMessage("PTP is ready. Select PTP On to start timing.", false) + } + } + } + Timer { id: messageTimer interval: 2600 From 763f3c9098d7b490dcb9486ec86b84c833c2c447 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 13:01:55 +0700 Subject: [PATCH 10/16] test(studio): guard default PTP and phase SET millidegrees end-to-end --- .../tests/check_operator_workspace.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/apps/arstack_studio/tests/check_operator_workspace.py b/apps/arstack_studio/tests/check_operator_workspace.py index fad605d99..14fa8771f 100644 --- a/apps/arstack_studio/tests/check_operator_workspace.py +++ b/apps/arstack_studio/tests/check_operator_workspace.py @@ -87,8 +87,19 @@ def main() -> None: require("anchors.centerIn: parent" in calm, "button caption needs horizontal centering") require("root.sendSignal(group, row)" in main_qml - and "Layout.fillHeight: false" in main_qml, - "table and sent phases must remain consistent") + and "Layout.fillHeight: false" in main_qml + and "Layout.preferredHeight: root.compactLayout ? 181 : 196" in main_qml, + "tables and live sent phases must use explicit bounded layout") + require('property bool ptpDefaultPending: true' in main_qml + and 'root.startPtpRole("SOURCE")' in main_qml + and "device.deviceVerified" in main_qml, + "default PTP must reuse verified session ownership") + device_cpp = (STUDIO / "src/DeviceController.cpp").read_text(encoding="utf-8") + require('commands << QStringLiteral("FREQ %1")' in device_cpp + and "const double phase = row.at(1).toDouble(&phaseOk);" in device_cpp + and "const double angleMdeg = phaseDegrees * 1000.0;" in device_cpp + and "commands << QStringLiteral(\"START\");" in device_cpp, + "START must send actual eight model phases as millidegree SET commands before START") matrix = source("SignalMatrix.qml") require("Layout.fillHeight: true" in matrix and "Layout.minimumHeight: 34" in matrix From 4d8455ba2d0e3c0b5c3302663bf76b2c9e843aa2 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 13:05:01 +0700 Subject: [PATCH 11/16] fix(studio): reuse existing one-shot PTP ON, never create duplicate timer --- apps/arstack_studio/qml/Main.qml | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/apps/arstack_studio/qml/Main.qml b/apps/arstack_studio/qml/Main.qml index dbd92b6f0..5ec0feed2 100644 --- a/apps/arstack_studio/qml/Main.qml +++ b/apps/arstack_studio/qml/Main.qml @@ -118,10 +118,6 @@ ApplicationWindow { // an explicit PTP ON can be restored after a firmware reflash/reconnect // without introducing an uncontrolled power-on publisher. property bool ptpDesiredForSession: false - // Default PTP SOURCE is a one-shot session convenience: only after - // verified firmware + a ready profile, never during recovery/flashing. - // An explicit PTP OFF cancels automatic rearming. - property bool ptpDefaultPending: true property string ptpDesiredRole: "SOURCE" property bool ptpRestoreAfterFirmware: false property int ptpRestoreChecks: 0 @@ -260,24 +256,6 @@ ApplicationWindow { } } - Timer { - interval: 350 - repeat: true - running: root.ptpDefaultPending - onTriggered: { - const session = workflowBar.session - if (!session || !device.deviceVerified || !session.startReady || - session.updatingFirmware || device.ptpStarting) - return - root.ptpDefaultPending = false - if (!device.ptpRunning) { - // Use the same configured, verified PTP START as the switch. - if (!root.startPtpRole("SOURCE")) - root.showMessage("PTP is ready. Select PTP On to start timing.", false) - } - } - } - Timer { id: messageTimer interval: 2600 From 315690b2517ac285aa61d76edf59e9ef6832983c Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 13:05:23 +0700 Subject: [PATCH 12/16] test(studio): validate compact stacked matrices and single pre-existing PTP default --- apps/arstack_studio/tests/check_operator_workspace.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/arstack_studio/tests/check_operator_workspace.py b/apps/arstack_studio/tests/check_operator_workspace.py index 14fa8771f..ea2d8c262 100644 --- a/apps/arstack_studio/tests/check_operator_workspace.py +++ b/apps/arstack_studio/tests/check_operator_workspace.py @@ -53,10 +53,10 @@ def main() -> None: and "root.pendingApply" in main_qml and "pendingApply = true" in main_qml, "table edits must expose unsent setpoints without claiming wire readback") - require("Layout.minimumHeight: root.compactLayout ? 180 : 210" in main_qml - and "Layout.fillHeight: true" in main_qml + require("Layout.preferredHeight: root.compactLayout ? 378 : 410" in main_qml + and "Layout.maximumHeight: 420" in main_qml and "ColumnLayout {" in main_qml, - "voltage and current must stack vertically and fill the workspace") + "voltage and current must stack vertically with bounded content height") require('property bool ptpDefaultStartAttempted: false' in main_qml and 'root.ptpDefaultStartAttempted = true' in main_qml and '!root.startPtpRole("SOURCE")' in main_qml, @@ -90,7 +90,7 @@ def main() -> None: and "Layout.fillHeight: false" in main_qml and "Layout.preferredHeight: root.compactLayout ? 181 : 196" in main_qml, "tables and live sent phases must use explicit bounded layout") - require('property bool ptpDefaultPending: true' in main_qml + require('property bool ptpDefaultStartAttempted: false' in main_qml and 'root.startPtpRole("SOURCE")' in main_qml and "device.deviceVerified" in main_qml, "default PTP must reuse verified session ownership") From 9659134d4888b57688a6b69a8522b988a45ca10c Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 13:07:23 +0700 Subject: [PATCH 13/16] docs(studio): pin observed PTP/SV continuity and GUI setpoint nonregression gate --- ..._SV_PTP_NO_REGRESSION_BASELINE_20261018.md | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 docs/STUDIO_SV_PTP_NO_REGRESSION_BASELINE_20261018.md diff --git a/docs/STUDIO_SV_PTP_NO_REGRESSION_BASELINE_20261018.md b/docs/STUDIO_SV_PTP_NO_REGRESSION_BASELINE_20261018.md new file mode 100644 index 000000000..dadb65f9a --- /dev/null +++ b/docs/STUDIO_SV_PTP_NO_REGRESSION_BASELINE_20261018.md @@ -0,0 +1,22 @@ +# ARStack Studio SV–PTP no-regression baseline — 18 Oct 2026 + +This document freezes observed evidence and operator-visible behavior without calling host-receiver timing a hardware accuracy certificate. + +## Known-good evidence + +The user's independent 120-second ASIX/Npcap capture demonstrated: 480,294 canonical SV frames (svID ARSTACK_SV01, APPID 0x4000), no diagnostic mirror, no smpCnt gaps/duplicates, 120 counter wraps, all smpSynch=1 under SOURCE + AUTO. PTP domain 0 carried 120 Announce, 120 Sync, and 120 matched Follow_Up. Host-relative smpCnt=0 to PTP epoch estimate median +48.9us, P95 +67.2us. This includes frame-publication and capture path delay; it is NOT a hardware sample-clock phase-error guarantee. + +## Non-regression gates + +1. Native Preview firmware must match the exact GUI commit and include PTP phase servo, MAC TX timing evidence, and broadcast mirror disabled. Never regress to a v0.1.1 bundle built from the old main firmware. +2. Local smpSynch=1 is qualified by actual sample timing; neither PTP visibility nor manual metadata suffices. Do not assert global smpSynch=2 for a local class-248 SOURCE. +3. At 50 Hz / 80 SPC, canonical SV rate is 4000 fps and smpCnt wraps 3999→0, with no healthy-stream discontinuities. +4. Every START flushes editor debounces and sends the eight current table setpoints as an ordered FREQ/SET/ENABLE/SHAPE/START batch; serial phase encoding is signed millidegrees (Ia −30.00° -> -30000). No silent reuse of stale ESP32-P4 setpoints. +5. Editing live phase-link behavior must use the same ListModels as the visible table and enqueue all affected phases. Do not claim on-wire equality without PCAP and timestamp-matched setpoints. +6. PTP SOURCE has exactly one owner for the verified-session default ON behavior; user OFF cancels it. Do not add duplicate startup timers, modal barriers, or write PTP/SMV clock state from presentation. +7. GUI: readable brand/logo, consistently centered buttons, dark text for pale hover, only one stable checkbox indicator, vertical voltage/current tables with bounded heights; preserve Windows DPI 100/150/175 acceptance. +8. GUI-only improvements must not alter Ethernet SV frames, PTP hardware epoch lease, timestamp/ISR behavior, wire quality, or smpCnt logic. + +## Next evidence for release + +Studio Qt CI, Windows Lifecycle and exact-head Native Preview must pass. After installation, inspect serial FREQ/SET phase commands and independent decoded waveforms for Ia −30°, Ib −150°, Ic +90° and the requested Ua/Ub/Uc. Capture >120 seconds after AUTO becomes local 1, include STOP/START and PTP SOURCE fault/recovery. Confirm no missing counters and stable epoch alignment. Physical synchronization accuracy and cross-vendor subscriber acceptance are separate from host timing screening. From 69a33f7b37448191a75bef3febc84720a91fb280 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 13:09:37 +0700 Subject: [PATCH 14/16] test(studio): update legacy tiny logo expectation to legible branded ribbon --- apps/arstack_studio/tests/check_operator_workspace.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/arstack_studio/tests/check_operator_workspace.py b/apps/arstack_studio/tests/check_operator_workspace.py index ea2d8c262..7bf13731c 100644 --- a/apps/arstack_studio/tests/check_operator_workspace.py +++ b/apps/arstack_studio/tests/check_operator_workspace.py @@ -117,9 +117,9 @@ def main() -> None: and main_qml.index("id: voltageMatrix") < main_qml.index("id: currentMatrix"), "Voltage and Current need a vertical, flexible-height editor workspace") ribbon_qml = source("ModernRibbon.qml") - require("Layout.preferredWidth: bar.compact ? 220 : 270" in ribbon_qml - and "font.pixelSize: bar.compact ? 20 : 22" in ribbon_qml - and "width: 44" in ribbon_qml, + require("Layout.preferredWidth: bar.compact ? 232 : 286" in ribbon_qml + and "font.pixelSize: bar.compact ? 21 : 25" in ribbon_qml + and "width: 53" in ribbon_qml, "SMV Injector branding must be readable at compact and normal widths") device_controller = (STUDIO / "src/DeviceController.cpp").read_text(encoding="utf-8") workflow = source("WorkflowBar.qml") From 858c8eab2ed475b1f60f03d29dcf096789978f06 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 13:16:54 +0700 Subject: [PATCH 15/16] fix(studio): keep stacked signal matrices within minimum 960x620 Windows viewport --- apps/arstack_studio/qml/Main.qml | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/apps/arstack_studio/qml/Main.qml b/apps/arstack_studio/qml/Main.qml index 5ec0feed2..30a7400ef 100644 --- a/apps/arstack_studio/qml/Main.qml +++ b/apps/arstack_studio/qml/Main.qml @@ -1098,17 +1098,21 @@ ApplicationWindow { // heights, never stretch four tiny rows into an // oversized empty card at tall window sizes. Layout.fillHeight: false - Layout.preferredHeight: root.compactLayout ? 378 : 410 - Layout.maximumHeight: 420 - Layout.minimumHeight: root.compactLayout ? 340 : 370 + // Reserve vertical room for frequency, timing, + // and quality controls even at 960x620. Larger + // windows can use full natural matrix height. + Layout.preferredHeight: Math.min(410, Math.max(215, root.height - 365)) + Layout.maximumHeight: 410 + Layout.minimumHeight: 195 spacing: 8 SignalMatrix { id: voltageMatrix enabled: !workflowBar.startQueued objectName: "studioVoltageMatrix" Layout.fillWidth: true - Layout.fillHeight: false - Layout.preferredHeight: root.compactLayout ? 181 : 196 + Layout.fillHeight: true + Layout.preferredHeight: 196 + Layout.minimumHeight: 92 theme: studioTheme controller: root device: device @@ -1127,8 +1131,9 @@ ApplicationWindow { enabled: !workflowBar.startQueued objectName: "studioCurrentMatrix" Layout.fillWidth: true - Layout.fillHeight: false - Layout.preferredHeight: root.compactLayout ? 181 : 196 + Layout.fillHeight: true + Layout.preferredHeight: 196 + Layout.minimumHeight: 92 theme: studioTheme controller: root device: device From 460979da6b54e0be14c9134e79780361f3a95c8e Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sun, 18 Oct 2026 13:17:15 +0700 Subject: [PATCH 16/16] test(studio): preserve responsive bounded stacked height at 960x620 and desktop viewports --- apps/arstack_studio/tests/check_operator_workspace.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/arstack_studio/tests/check_operator_workspace.py b/apps/arstack_studio/tests/check_operator_workspace.py index 7bf13731c..68087b5b6 100644 --- a/apps/arstack_studio/tests/check_operator_workspace.py +++ b/apps/arstack_studio/tests/check_operator_workspace.py @@ -53,8 +53,8 @@ def main() -> None: and "root.pendingApply" in main_qml and "pendingApply = true" in main_qml, "table edits must expose unsent setpoints without claiming wire readback") - require("Layout.preferredHeight: root.compactLayout ? 378 : 410" in main_qml - and "Layout.maximumHeight: 420" in main_qml + require("Layout.preferredHeight: Math.min(410, Math.max(215, root.height - 365))" in main_qml + and "Layout.maximumHeight: 410" in main_qml and "ColumnLayout {" in main_qml, "voltage and current must stack vertically with bounded content height") require('property bool ptpDefaultStartAttempted: false' in main_qml @@ -88,7 +88,7 @@ def main() -> None: "button caption needs horizontal centering") require("root.sendSignal(group, row)" in main_qml and "Layout.fillHeight: false" in main_qml - and "Layout.preferredHeight: root.compactLayout ? 181 : 196" in main_qml, + and "Layout.preferredHeight: 196" in main_qml, "tables and live sent phases must use explicit bounded layout") require('property bool ptpDefaultStartAttempted: false' in main_qml and 'root.startPtpRole("SOURCE")' in main_qml