Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
b672d79
fix(studio): always apply all eight visible table setpoints before SV…
masarray Oct 17, 2026
973f1f6
style(studio): stack full-width 4V and 4I matrices to use vertical wo…
masarray Oct 17, 2026
3bd2570
style(studio): scale operator controls, align labels and unify hover …
masarray Oct 17, 2026
c2e2f3f
style(studio): scale operator controls, align labels and unify hover …
masarray Oct 17, 2026
37f88c2
style(studio): scale operator controls, align labels and unify hover …
masarray Oct 17, 2026
e073624
style(studio): scale operator controls, align labels and unify hover …
masarray Oct 17, 2026
00ef28b
feat(studio): default PTP SOURCE ON when verified, while OFF remains …
masarray Oct 17, 2026
e4ec60d
style(studio): enforce dark readable captions on light hover for ever…
masarray Oct 17, 2026
b79d330
style(studio): compact full-width matrix rows without clipping stacke…
masarray Oct 17, 2026
6509e4b
style(studio): standardize white hover with black caption in Advanced…
masarray Oct 17, 2026
229cf04
style(studio): standardize white hover with black caption in Advanced…
masarray Oct 17, 2026
420fc04
style(studio): expand stacked voltage/current panels to fill availabl…
masarray Oct 17, 2026
d4a7423
test(studio): guard table-to-wire START, default PTP, stacked layout …
masarray Oct 17, 2026
778dbdc
test(studio): initialize workflow source before new operator regressi…
masarray Oct 17, 2026
3ecddad
test(studio): preserve scenario START guard when source formatting sp…
masarray Oct 17, 2026
0c68565
style(studio): eliminate empty bottom spacer beneath full-height sign…
masarray Oct 17, 2026
9185228
test(studio): accept wrapped always-stage START call in older Edit-to…
masarray Oct 17, 2026
ef43c2b
fix(studio): scroll stacked injection workspace only when viewport he…
masarray Oct 17, 2026
f36bd99
test(windows): validate stacked 4V/4I scroll reachability at 960x620,…
masarray Oct 17, 2026
2529638
test(studio): lock responsive stacked matrices and minimum-viewport s…
masarray Oct 17, 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
11 changes: 6 additions & 5 deletions apps/arstack_studio/qml/CalmButton.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 5 additions & 2 deletions apps/arstack_studio/qml/ConfigurationHub.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions apps/arstack_studio/qml/DarkToolButton.qml
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@ 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
}
}
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
}
Expand Down
64 changes: 55 additions & 9 deletions apps/arstack_studio/qml/Main.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment on lines +121 to +122

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 Route Advanced PTP actions through session intent

With the session intent now initially true, an operator who turns PTP off from the Advanced panel does not cancel that intent: both Advanced stop paths call session.requestStopPtp() directly in PtpExpertPanel.qml rather than root.requestStopPtp(). After a later successful firmware update, onFirmwareUpdateFinished still sees this property as true and automatically restarts PTP SOURCE despite the explicit OFF; Advanced starts likewise do not update ptpDesiredRole, so a selected receiver/monitor role can be restored as SOURCE. Route all PTP start/stop entry points through the intent-owning controller.

AGENTS.md reference: AGENTS.md:L74-L86

Useful? React with 👍 / 👎.

property string ptpDesiredRole: "SOURCE"
property bool ptpRestoreAfterFirmware: false
property int ptpRestoreChecks: 0
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -323,6 +344,7 @@ ApplicationWindow {
}

function requestStopPtp() {
root.defaultPtpStartAttempted = true
root.ptpDesiredForSession = false
root.ptpRestoreAfterFirmware = false
return workflowBar.session && workflowBar.session.requestStopPtp()
Expand Down Expand Up @@ -930,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
Expand Down Expand Up @@ -1049,17 +1088,19 @@ ApplicationWindow {
}
}

RowLayout {
ColumnLayout {

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 Update the viewport assertion for stacked matrices

Changing this container from RowLayout to ColumnLayout makes the voltage and current matrices occupy the same horizontal range, but checkOperatorViewport() in apps/arstack_studio/src/main.cpp:85 still requires voltage.area.right() <= current.area.left() + tolerance. Consequently every --check-operator-ui run reaches the panel-order failure and exits 21, including the Windows lifecycle geometry job; update that acceptance check to assert the new vertical ordering and containment.

AGENTS.md reference: AGENTS.md:L249-L263

Useful? React with 👍 / 👎.

Layout.fillWidth: true
Layout.preferredHeight: root.compactLayout ? 214 : 224
Layout.maximumHeight: root.compactLayout ? 214 : 224
spacing: 9
Layout.fillHeight: true
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
theme: studioTheme
controller: root
device: device
Expand All @@ -1079,6 +1120,8 @@ ApplicationWindow {
objectName: "studioCurrentMatrix"
Layout.fillWidth: true
Layout.fillHeight: true
Layout.preferredHeight: 205
Layout.minimumHeight: 205
theme: studioTheme
controller: root
device: device
Expand Down Expand Up @@ -1136,7 +1179,10 @@ ApplicationWindow {
compact: root.compactLayout
}
}
Item { Layout.fillHeight: true }
// Stacked voltage/current matrices fill the full
// desktop panel; Flickable preserves all controls
// on smaller Windows viewports without clipping.
}
}
}

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 ? 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
}
}

Expand All @@ -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
Expand Down
5 changes: 3 additions & 2 deletions apps/arstack_studio/qml/RibbonAction.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion apps/arstack_studio/qml/SignalMatrix.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
11 changes: 8 additions & 3 deletions apps/arstack_studio/qml/StudioDarkCheckBox.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions apps/arstack_studio/qml/StudioDarkComboBox.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
}

Expand Down
9 changes: 6 additions & 3 deletions apps/arstack_studio/qml/WorkflowBar.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
49 changes: 36 additions & 13 deletions apps/arstack_studio/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::pair<const char*, UiBounds>, 6> areas{{
const auto scroll = findUiBounds(window, "studioInjectionScroll");
const auto* scrollItem = window->findChild<QQuickItem*>(
QStringLiteral("studioInjectionScroll"));
const qreal scrollContentHeight = scrollItem != nullptr
? scrollItem->property("contentHeight").toDouble() : 0.0;
const bool needsScroll = scrollContentHeight > scroll.area.height() + tolerance;
const std::array<std::pair<const char*, UiBounds>, 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<const char*, 4> controls{{
Expand Down
Loading
Loading