Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
f20e673
fix(studio): every START atomically queues current 4I+4V table snapshot
masarray Oct 17, 2026
b25bf57
style(studio): vertical voltage-current tables fill injection workspace
masarray Oct 17, 2026
289167f
style(studio): normalize branding, centered controls and accessible A…
masarray Oct 17, 2026
898f62c
style(studio): normalize branding, centered controls and accessible A…
masarray Oct 17, 2026
c4d41bf
style(studio): normalize branding, centered controls and accessible A…
masarray Oct 17, 2026
7e62cfd
style(studio): normalize branding, centered controls and accessible A…
masarray Oct 17, 2026
bd4003a
style(studio): normalize branding, centered controls and accessible A…
masarray Oct 17, 2026
e772464
test(studio): lock full-table START, stacked matrices and operator UX…
masarray Oct 17, 2026
278439c
test(studio): accept formatted full-snapshot START call in existing c…
masarray Oct 17, 2026
6de9f25
test(studio): update final legacy one-line Start snapshot assertion
masarray Oct 17, 2026
c02ed99
feat(studio): default verified-device PTP SOURCE ON, user OFF cancels…
masarray Oct 17, 2026
1d28395
feat(studio): default verified-device PTP SOURCE ON, user OFF cancels…
masarray Oct 17, 2026
1cabe36
feat(studio): default verified-device PTP SOURCE ON, user OFF cancels…
masarray Oct 17, 2026
93f46bc
feat(studio): default verified-device PTP SOURCE ON, user OFF cancels…
masarray Oct 17, 2026
5dde042
test(studio): initialize workflow contract source before early PTP de…
masarray Oct 18, 2026
7b35a2c
test(studio): preserve Windows viewport overlap gate for stacked volt…
masarray Oct 18, 2026
590244f
fix(studio): responsive stacked signal matrices without 960x620 viewp…
masarray Oct 18, 2026
82cdd0a
test(studio): assert stacked desktop and side-by-side compact geometr…
masarray Oct 18, 2026
98fae91
test(studio): keep 960x620 compact geometry while stacking at normal …
masarray Oct 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/arstack_studio/qml/CalmButton.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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 } }
}
}
Expand Down
6 changes: 4 additions & 2 deletions apps/arstack_studio/qml/ConfigurationHub.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
25 changes: 17 additions & 8 deletions apps/arstack_studio/qml/Main.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1049,17 +1049,25 @@ ApplicationWindow {
}
}

RowLayout {
// 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.preferredHeight: root.compactLayout ? 214 : 224
Layout.maximumHeight: root.compactLayout ? 214 : 224
spacing: 9
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
objectName: "studioVoltageMatrix"
Layout.fillWidth: true
Layout.fillHeight: true
Layout.minimumHeight: 193
theme: studioTheme
controller: root
device: device
Expand All @@ -1079,6 +1087,7 @@ ApplicationWindow {
objectName: "studioCurrentMatrix"
Layout.fillWidth: true
Layout.fillHeight: true
Layout.minimumHeight: 193
theme: studioTheme
controller: root
device: device
Expand Down
18 changes: 9 additions & 9 deletions apps/arstack_studio/qml/ModernRibbon.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion apps/arstack_studio/qml/PtpExpertPanel.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand Down
1 change: 1 addition & 0 deletions apps/arstack_studio/qml/RibbonAction.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion apps/arstack_studio/qml/StudioDarkCheckBox.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
17 changes: 12 additions & 5 deletions apps/arstack_studio/qml/WorkflowBar.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -281,9 +286,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)
Expand Down
10 changes: 8 additions & 2 deletions apps/arstack_studio/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,14 @@ struct UiBounds {
return false;
}
}
if (!(voltage.area.right() <= current.area.left() + tolerance
&& tray.area.top() >= voltage.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
Expand Down
31 changes: 27 additions & 4 deletions apps/arstack_studio/tests/check_operator_workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -46,14 +47,36 @@ 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")

ordered(main_qml, "id: voltageMatrix", "id: currentMatrix",
"voltage must precede current in the injection table")
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,
Comment on lines +66 to +67

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Make the START regression checks agree on formatting

This check requires the newly split two-line call, while the same test later still searches for the exact one-line substring requestStartWithScenario(controller.startScenarioSnapshot()) at lines 135–167. Consequently, running python3 apps/arstack_studio/tests/check_operator_workspace.py exits with reconnect must not auto-transmit..., and the Qt workflow that invokes this script always fails; consolidate these assertions or make them whitespace-insensitive.

Useful? React with 👍 / 👎.

"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)",
Expand Down Expand Up @@ -116,7 +139,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")
Expand Down Expand Up @@ -145,7 +168,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
Expand Down
Loading