Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
67e3a85
fix(studio): track bounded coalesced readbacks behind exclusive devic…
masarray Oct 21, 2026
1658560
fix(studio): defer coalesced readbacks across CFG and binary transact…
masarray Oct 21, 2026
d4d8955
fix(studio): commit setpoint drafts once on Enter, Tab or focus loss
masarray Oct 21, 2026
a9635a4
fix(studio): compact 1024 workbench, equal phasor waveform, safe nume…
masarray Oct 21, 2026
c6a09a1
fix(studio): make PTP details a compact accessible icon control
masarray Oct 21, 2026
dd0d31a
fix(studio): enforce single width fill and 50/50 resizable chart geom…
masarray Oct 21, 2026
a732b7d
test(studio): verify coalesced busy polling and STOP unconfirmed afte…
masarray Oct 21, 2026
45bfecf
test(studio): update compact UX contracts and reject early commits/hi…
masarray Oct 21, 2026
2be8b4a
test(studio): expose real chart bounds for Qt viewport regression
masarray Oct 21, 2026
4c04f4f
test(studio): enforce real five-width Qt geometry and best-effort lar…
masarray Oct 21, 2026
62fcec4
ci(studio): inspect five widths and multiple DPI; upload available 10…
masarray Oct 21, 2026
f5d3548
build(studio): include cmath for explicit viewport proportion check
masarray Oct 21, 2026
58fe1a4
fix(studio): preserve PTP board authority while STOP readback is defe…
masarray Oct 21, 2026
d0216e8
test(studio): stale PTP OFF telemetry cannot confirm pending STOP
masarray Oct 21, 2026
6554461
fix(studio): discard coalesced telemetry across session generation an…
masarray Oct 21, 2026
857e309
fix(studio): suspend health probe misses during ordered CFG transaction
masarray Oct 21, 2026
ce195c1
test(studio): health misses deferred only while CFG owns serial; real…
masarray Oct 21, 2026
3db28f0
test(studio): fail native screenshots when phasor or waveform is missing
masarray Oct 21, 2026
8d0a558
fix(studio): restore visible phasor and waveform on first run and mig…
masarray Oct 21, 2026
b3cc1bf
test(studio): lock both chart docks visible on first run and legacy p…
masarray Oct 21, 2026
f423fa6
fix(studio): size injection controls by narrow panel, not desktop; re…
masarray Oct 21, 2026
9159db6
fix(studio): fit PTP role, smpSynch and details within 405px injectio…
masarray Oct 21, 2026
67f5491
fix(studio): compact IEC quality controls without truncating 32-bit mask
masarray Oct 21, 2026
e7ee461
test(studio): reject injection child overflow even if it remains insi…
masarray Oct 21, 2026
6d81ad3
ci(studio): test 125% DPI alongside 100, 150 and 175%
masarray Oct 21, 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: 7 additions & 4 deletions .github/workflows/arstack-studio-windows-lifecycle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,14 @@ jobs:
New-Item -ItemType Directory -Path 'studio-ui-evidence' -Force | Out-Null
Remove-Item Env:ARSTACK_STUDIO_QA_DIR -ErrorAction SilentlyContinue
Remove-Item Env:ARSTACK_STUDIO_QA_NATIVE_ONLY -ErrorAction SilentlyContinue
foreach ($scale in @('1', '1.5', '1.75')) {
foreach ($scale in @('1', '1.25', '1.5', '1.75')) {
$env:QT_SCALE_FACTOR = $scale
$suffix = 'dpi-' + [string][int]([double]::Parse($scale, [System.Globalization.CultureInfo]::InvariantCulture) * 100)
$logdir = Join-Path (Join-Path $PWD 'studio-ui-evidence') $suffix
New-Item -ItemType Directory -Path $logdir -Force | Out-Null
# Software viewport shots, when grabWindow supports the CI backend.
# Native Windows fidelity is verified separately below.
$env:ARSTACK_STUDIO_QA_DIR = $logdir
$stdout = Join-Path $logdir 'geometry-stdout.txt'
$stderr = Join-Path $logdir 'geometry-stderr.txt'
$probe = Start-Process -FilePath $exe -ArgumentList '--check-operator-ui' -Wait -PassThru `
Expand Down Expand Up @@ -136,11 +139,11 @@ jobs:
if ($probe.ExitCode -ne 0) {
throw "Native Windows screenshot acceptance failed, exit=$($probe.ExitCode)"
}
$png = Join-Path $env:ARSTACK_STUDIO_QA_DIR 'studio-960x620.png'
$png = Join-Path $env:ARSTACK_STUDIO_QA_DIR 'studio-960x680.png'
if (-not (Test-Path $png) -or (Get-Item $png).Length -lt 10000) {
throw 'Native Windows 960x620 screenshot was missing or empty'
throw 'Native Windows 960x680 screenshot was missing or empty'
}
Write-Host 'Windows Qt deterministic 3-viewports x 3-DPI geometry + native 960x620 screenshot: PASS'
Write-Host 'Windows Qt deterministic 5-viewports x 4-DPI geometry + native 960x680 screenshot: PASS'
- name: Upload desktop layout evidence
if: always()
uses: actions/upload-artifact@v4
Expand Down
84 changes: 44 additions & 40 deletions apps/arstack_studio/qml/Main.qml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import ARStack.Studio 1.0

ApplicationWindow {
id: root
width: 1150
height: 790
minimumWidth: 960
minimumHeight: 620
width: 1024
height: 748
minimumWidth: 940
minimumHeight: 640
visible: true
title: "ARStack Studio · SMV + PTP · v" + Qt.application.version
color: studioTheme.bg
Expand Down Expand Up @@ -432,15 +432,16 @@ ApplicationWindow {

function parseOperatorNumber(text) {
var normalized = String(text).trim()
.replace(/\s/g, "")
.replace(/degrees?/ig, "")
.replace(/deg/ig, "")
.replace(/°/g, "")
.replace(/Hz/ig, "")
.replace(/[AV]$/i, "")
.replace(/[AV]$/i, "").trim()
// Decimal comma and dot are equally valid. Never interpret mixed
// punctuation or thousands-grouping as a silently altered number.
if (normalized.indexOf(",") >= 0 && normalized.indexOf(".") < 0)
normalized = normalized.replace(",", ".")
if (!normalized.length || normalized === "+" || normalized === "-" || normalized === ".")
if (!/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/.test(normalized))
return NaN
return Number(normalized)
}
Expand Down Expand Up @@ -575,8 +576,8 @@ ApplicationWindow {
return ok
}
function startScenarioSnapshot() {
// The table editors debounce keyboard edits for live preview. Flush
// them synchronously before creating the ordered board START payload.
// Flush every local draft once before serializing the canonical
// 4I+4V scenario. An invalid draft cannot be sent to the board.
if (!currentMatrix.flushPendingEdits() || !voltageMatrix.flushPendingEdits()) {
showMessage("Invalid signal input: correct highlighted field before Start.", true)
return null
Expand Down Expand Up @@ -1063,23 +1064,34 @@ ApplicationWindow {

SurfacePanel {
objectName: "studioInjectionSurface"
SplitView.fillWidth: true
SplitView.fillHeight: true
SplitView.minimumWidth: root.compactLayout ? 500 : 540
SplitView.preferredWidth: root.compactLayout ? 600 : 690
SplitView.maximumWidth: 750
SplitView.minimumWidth: 405
SplitView.preferredWidth: 425
SplitView.maximumWidth: 465
SplitView.fillWidth: false
theme: studioTheme

// Clicking unoccupied workspace commits the focused cell.
// Controls above this background retain their own taps.
MouseArea {
anchors.fill: parent
onClicked: root.contentItem.forceActiveFocus()
}

ColumnLayout {
z: 1
anchors.fill: parent
anchors.margins: root.compactLayout ? 11 : 13
spacing: root.compactLayout ? 6 : 8
// Injection width stays 405..465px at every desktop size.
// Never expand its children just because the whole window
// crosses the 1220px toolbar breakpoint.
anchors.margins: 10
spacing: 6

RowLayout {
Layout.fillWidth: true
ColumnLayout {
spacing: 1
Label { text: "4I + 4V Injection"; color: studioTheme.text; font.family: root.uiFont; font.pixelSize: root.compactLayout ? 17 : 18; font.weight: Font.DemiBold }
Label { text: "4I + 4V Injection"; color: studioTheme.text; font.family: root.uiFont; font.pixelSize: 17; font.weight: Font.DemiBold }
}
Item { Layout.fillWidth: true }
// START owns the single ordered scenario transaction.
Expand Down Expand Up @@ -1140,7 +1152,7 @@ ApplicationWindow {
}
theme: studioTheme
monoFont: root.monoFont
compact: root.compactLayout
compact: true
implicitWidth: 90
text: "50.000"
suffixText: "Hz"
Expand Down Expand Up @@ -1168,10 +1180,6 @@ ApplicationWindow {
}
}

CalmButton { theme: studioTheme; uiFont: root.uiFont; text: "0"; implicitWidth: 44; toolTipText: "DC"; onClicked: root.setFrequencyValue(0) }
CalmButton { theme: studioTheme; uiFont: root.uiFont; text: "50"; implicitWidth: 44; onClicked: root.setFrequencyValue(50) }
CalmButton { theme: studioTheme; uiFont: root.uiFont; text: "60"; implicitWidth: 44; onClicked: root.setFrequencyValue(60) }
Rectangle { width: 1; height: 24; color: studioTheme.lineSoft }
StudioLinkSwitch {
theme: studioTheme
uiFont: root.uiFont
Expand Down Expand Up @@ -1221,7 +1229,7 @@ ApplicationWindow {
titleText: "Voltage"
symbolText: "U"
unitText: root.signalFrequency === 0 ? "V DC" : "V RMS"
compact: root.compactLayout
compact: true
}
SignalMatrix {
id: currentMatrix
Expand All @@ -1242,7 +1250,7 @@ ApplicationWindow {
titleText: "Current"
symbolText: "I"
unitText: root.signalFrequency === 0 ? "A DC" : "A RMS"
compact: root.compactLayout
compact: true
}

}
Expand All @@ -1258,23 +1266,18 @@ ApplicationWindow {
session: workflowBar.session
uiFont: root.uiFont
monoFont: root.monoFont
compact: root.compactLayout
compact: true
}
Rectangle { Layout.fillWidth: true; height: 1; color: studioTheme.lineSoft }
RowLayout {
objectName: "studioQualityRow"
Layout.fillWidth: true
spacing: 8
spacing: 6
Rectangle { width: 6; height: 6; radius: 3; color: studioTheme.accent }
Label { text: root.activeSignal; color: studioTheme.textSoft; font.family: root.monoFont; font.pixelSize: studioTheme.labelSize; font.weight: Font.Bold }
Label {
text: root.signalFrequency === 0
? root.activeMagnitude.toFixed(3) + " " + root.activeUnit + " DC"
: root.activeMagnitude.toFixed(3) + " " + root.activeUnit + " RMS · " + root.activePhase.toFixed(2) + "°"
color: studioTheme.muted
font.family: root.uiFont
font.pixelSize: 9
}
// The same magnitude/phase is already visible in
// the active matrix row and plots. Removing this
// duplicate avoids squeezing the quality controls.
Rectangle { width: 1; height: 20; color: studioTheme.lineSoft }
QualityEditor {
enabled: !workflowBar.startQueued
Expand All @@ -1285,7 +1288,7 @@ ApplicationWindow {
uiFont: root.uiFont
monoFont: root.monoFont
qualityValue: root.activeQuality
compact: root.compactLayout
compact: true
}
}
// No trailing filler: vertically stacked matrices
Expand All @@ -1296,9 +1299,9 @@ ApplicationWindow {
SplitView {
visible: (root.phasorDockVisible && !root.phasorDetached) ||
(root.waveformDockVisible && !root.waveformDetached)
SplitView.preferredWidth: root.compactLayout ? 450 : 470
SplitView.minimumWidth: root.compactLayout ? 300 : 340
SplitView.maximumWidth: 680
SplitView.preferredWidth: 560
SplitView.minimumWidth: 390
SplitView.fillWidth: true
SplitView.fillHeight: true
orientation: Qt.Vertical
handle: Rectangle {
Expand All @@ -1315,11 +1318,11 @@ ApplicationWindow {
}

DockFrame {
objectName: "studioPhasorDock"
visible: root.phasorDockVisible && !root.phasorDetached
SplitView.fillWidth: true
SplitView.minimumHeight: 170
SplitView.preferredHeight: 225
SplitView.maximumHeight: 265
SplitView.preferredHeight: Math.max(170, (workspaceSplit.height - 9) / 2)
SplitView.fillHeight: false
theme: studioTheme
titleText: "Phasor"
Expand Down Expand Up @@ -1355,10 +1358,11 @@ ApplicationWindow {
}

DockFrame {
objectName: "studioWaveformDock"
visible: root.waveformDockVisible && !root.waveformDetached
SplitView.fillWidth: true
SplitView.minimumHeight: 170
SplitView.preferredHeight: 328
SplitView.preferredHeight: Math.max(170, (workspaceSplit.height - 9) / 2)
SplitView.fillHeight: true
theme: studioTheme
titleText: "Waveform"
Expand Down
4 changes: 2 additions & 2 deletions apps/arstack_studio/qml/QualityEditor.qml
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ RowLayout {
theme: editor.theme
uiFont: editor.uiFont
id: presetBox
implicitWidth: editor.compact ? 98 : 108
implicitWidth: editor.compact ? 90 : 108
model: ["Good", "Invalid", "Questionable", "Test"]
currentIndex: editor.presetIndex(editor.qualityValue)
font.family: editor.uiFont
Expand All @@ -116,7 +116,7 @@ RowLayout {
theme: editor.theme
monoFont: editor.monoFont
compact: true
implicitWidth: editor.compact ? 100 : 112
implicitWidth: editor.compact ? 98 : 112
Component.onCompleted: text = editor.hexText(editor.qualityValue)
onEditingFinished: {
var trimmed = text.trim()
Expand Down
Loading
Loading