Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
95746c7
fix(studio): strengthen phase table SSOT and operator workspace legib…
masarray Oct 18, 2026
8f900dd
fix(studio): strengthen phase table SSOT and operator workspace legib…
masarray Oct 18, 2026
ca0f4be
fix(studio): strengthen phase table SSOT and operator workspace legib…
masarray Oct 18, 2026
4bff585
fix(studio): strengthen phase table SSOT and operator workspace legib…
masarray Oct 18, 2026
51b0716
fix(studio): strengthen phase table SSOT and operator workspace legib…
masarray Oct 18, 2026
3995051
fix(qml): center button row in valid Item content root
masarray Oct 18, 2026
87452b3
fix(qml): keep checkbox label visible with single custom indicator
masarray Oct 18, 2026
a34492b
test(studio): enforce valid centered controls and one checkbox indicator
masarray Oct 18, 2026
7a9a00e
feat(studio): default verified-session PTP SOURCE ON without extra di…
masarray Oct 18, 2026
763f3c9
test(studio): guard default PTP and phase SET millidegrees end-to-end
masarray Oct 18, 2026
4d8455b
fix(studio): reuse existing one-shot PTP ON, never create duplicate t…
masarray Oct 18, 2026
315690b
test(studio): validate compact stacked matrices and single pre-existi…
masarray Oct 18, 2026
9659134
docs(studio): pin observed PTP/SV continuity and GUI setpoint nonregr…
masarray Oct 18, 2026
69a33f7
test(studio): update legacy tiny logo expectation to legible branded …
masarray Oct 18, 2026
858c8ea
fix(studio): keep stacked signal matrices within minimum 960x620 Wind…
masarray Oct 18, 2026
460979d
test(studio): preserve responsive bounded stacked height at 960x620 a…
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
12 changes: 8 additions & 4 deletions apps/arstack_studio/qml/CalmButton.qml
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,13 @@ Button {
tone === "danger" ? "#ffdce0" :
tone === "accent" ? "#dcebff" : theme.textSoft

contentItem: Row {
// Center the complete icon+caption pair in both axes in Qt Controls.
anchors.centerIn: parent
spacing: control.iconSource.toString().length > 0 ? 7 : 0
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
Expand All @@ -61,6 +64,7 @@ Button {
anchors.verticalCenter: parent.verticalCenter
Behavior on color { ColorAnimation { duration: 90 } }
}
}
}

ToolTip.visible: hovered && toolTipText.length > 0
Expand Down
36 changes: 28 additions & 8 deletions apps/arstack_studio/qml/Main.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment on lines +1083 to +1084

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 Keep the draft pending until every SET is admitted

When the device command queue is full or locked by an exclusive transaction, each sendSignal() still returns true because DeviceController::sendCommandBatch() reports only whether the queued Qt invocation was scheduled; the actual admission failure occurs later in DeviceIoWorker::enqueueCommandsInternal(). Consequently, toggling 3-phase link can reject some or all eight SETs while this line clears pendingApply, falsely presenting the visible table as applied. Send these SETs as one admission-checked batch or retain the pending state until worker acceptance is reported.

AGENTS.md reference: AGENTS.md:L116-L116

Useful? React with 👍 / 👎.

else root.showMessage("Not all live phases were sent. Stop/Start to apply the table.", true)
}
}
font.family: root.uiFont
font.pixelSize: 9
Expand All @@ -1080,21 +1094,25 @@ 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
// 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: true
Layout.preferredHeight: 196
Layout.minimumHeight: 92
theme: studioTheme
controller: root
device: device
Expand All @@ -1114,6 +1132,8 @@ ApplicationWindow {
objectName: "studioCurrentMatrix"
Layout.fillWidth: true
Layout.fillHeight: true
Layout.preferredHeight: 196
Layout.minimumHeight: 92
theme: studioTheme
controller: root
device: device
Expand Down
16 changes: 8 additions & 8 deletions apps/arstack_studio/qml/ModernRibbon.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
3 changes: 3 additions & 0 deletions apps/arstack_studio/qml/StudioDarkCheckBox.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
// 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 }
Expand Down
36 changes: 30 additions & 6 deletions apps/arstack_studio/tests/check_operator_workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -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: 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 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,
Expand All @@ -76,6 +76,30 @@ 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")
Comment on lines +82 to +84

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 Replace the stale structural assertions

The Qt CI workflow runs this script, but the newly required layout and branding strings contradict assertions that remain later in the same function. Running python3 apps/arstack_studio/tests/check_operator_workspace.py on this commit already exits at the old Layout.minimumHeight: root.compactLayout ? 180 : 210 requirement; after updating that, lines 120–122 still require the old 220 : 270, 20 : 22, and width: 44 branding that this new assertion and ModernRibbon.qml explicitly replace. The gate therefore cannot pass until the obsolete expectations are removed or updated.

Useful? React with 👍 / 👎.

require("indicator: Rectangle {" in checkbox,
"checkbox must keep one stable custom indicator on hover")
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
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
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
Expand All @@ -93,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")
Expand Down
22 changes: 22 additions & 0 deletions docs/STUDIO_SV_PTP_NO_REGRESSION_BASELINE_20261018.md
Original file line number Diff line number Diff line change
@@ -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.
Loading