Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .github/workflows/ied-simulator-qt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,9 @@ jobs:
apps/ied_simulator/qml/FileWorkspace.qml
apps/ied_simulator/qml/Main.qml

- name: Browser ribbon legibility contract
run: python3 apps/ied_simulator/test_browser_ribbon_legibility.py

- name: Browser convergence contract
run: >-
python3 apps/ied_simulator/test_browser_convergence.py
Expand Down
102 changes: 33 additions & 69 deletions apps/ied_simulator/qml/IedBrowserWorkspace.qml
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,20 @@ Item {
required property string badge
property bool emphasized: false

implicitWidth: 66
implicitHeight: 64
implicitWidth: 80
implicitHeight: 86
hoverEnabled: true
leftPadding: 4
rightPadding: 4
leftPadding: 3
rightPadding: 3

contentItem: Column {
spacing: 3
spacing: 5
anchors.centerIn: parent
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
width: 27
height: 25
radius: 4
width: 37
height: 35
radius: 6
color: action.enabled
? (action.emphasized ? root.theme.accent : root.theme.surfaceSoft)
: root.theme.surfaceRaised
Expand All @@ -58,18 +58,22 @@ Item {
color: action.enabled
? (action.emphasized ? "#ffffff" : root.theme.textSoft)
: root.theme.muted
font.pixelSize: action.badge.length > 2 ? 7 : 9
font.pixelSize: action.badge.length > 2 ? 12 : 16
font.weight: Font.DemiBold
}
}
Label {
anchors.horizontalCenter: parent.horizontalCenter
width: 62
width: 77
height: 33
text: action.text
color: action.enabled ? root.theme.text : root.theme.muted
font.pixelSize: 8
font.weight: Font.Medium
font.pixelSize: 11
font.weight: Font.DemiBold
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignTop
wrapMode: Text.WordWrap
maximumLineCount: 2
elide: Text.ElideRight
}
}
Expand Down Expand Up @@ -190,7 +194,7 @@ Item {

Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 94
Layout.preferredHeight: 112
Comment thread
masarray marked this conversation as resolved.
color: root.theme.chrome
border.width: 1
border.color: root.theme.lineSoft
Expand All @@ -210,7 +214,7 @@ Item {
spacing: 7

Column {
width: 136
width: 168
height: parent.height
Row {
RibbonAction {
Expand All @@ -234,15 +238,15 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter
text: "APPLICATION"
color: root.theme.muted
font.pixelSize: 7
font.pixelSize: 10
font.weight: Font.DemiBold
}
}

Rectangle { width: 1; height: 78; y: 5; color: root.theme.lineSoft }
Rectangle { width: 1; height: 96; y: 8; color: root.theme.lineSoft }

Column {
width: 262
width: 282
height: parent.height
spacing: 4
Row {
Expand All @@ -255,7 +259,7 @@ Item {
placeholderText: "IED IP / hostname"
selectByMouse: true
enabled: !session.configurationLocked
font.pixelSize: 9
font.pixelSize: 12
onEditingFinished: session.host = text
}
SpinBox {
Expand Down Expand Up @@ -284,7 +288,7 @@ Item {
width: 244
text: session.stateText + " · " + session.endpoint
color: session.lastError.length ? root.theme.red : root.theme.textSoft
font.pixelSize: 8
font.pixelSize: 11
font.weight: Font.Medium
elide: Text.ElideRight
}
Expand All @@ -293,15 +297,15 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter
text: "ENDPOINT"
color: root.theme.muted
font.pixelSize: 7
font.pixelSize: 10
font.weight: Font.DemiBold
}
}

Rectangle { width: 1; height: 78; y: 5; color: root.theme.lineSoft }
Rectangle { width: 1; height: 96; y: 8; color: root.theme.lineSoft }

Column {
width: 200
width: 246
height: parent.height
Row {
RibbonAction {
Expand Down Expand Up @@ -336,15 +340,15 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter
text: "IED SESSION"
color: root.theme.muted
font.pixelSize: 7
font.pixelSize: 10
font.weight: Font.DemiBold
}
}

Rectangle { width: 1; height: 78; y: 5; color: root.theme.lineSoft }
Rectangle { width: 1; height: 96; y: 8; color: root.theme.lineSoft }

Column {
width: 200
width: 246
height: parent.height
Row {
RibbonAction {
Expand Down Expand Up @@ -375,15 +379,15 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter
text: "DATA"
color: root.theme.muted
font.pixelSize: 7
font.pixelSize: 10
font.weight: Font.DemiBold
}
}

Rectangle { width: 1; height: 78; y: 5; color: root.theme.lineSoft }
Rectangle { width: 1; height: 96; y: 8; color: root.theme.lineSoft }

Column {
width: 266
width: 332
height: parent.height
Row {
RibbonAction {
Expand Down Expand Up @@ -425,52 +429,12 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter
text: "SERVICES"
color: root.theme.muted
font.pixelSize: 7
font.pixelSize: 10
font.weight: Font.DemiBold
}
}

Rectangle { width: 1; height: 78; y: 5; color: root.theme.lineSoft }

Column {
width: 176
height: parent.height
y: 6
spacing: 2
Label {
width: parent.width
text: "MODEL"
color: root.theme.muted
font.pixelSize: 7
font.weight: Font.DemiBold
}
Label {
width: parent.width
text: context.iedName.length ? context.iedName
: context.selectionRequired ? "Select active IED" : "IED model"
color: root.theme.text
font.pixelSize: 10
font.weight: Font.DemiBold
elide: Text.ElideRight
}
Label {
width: parent.width
text: context.loaded
? context.logicalDeviceCount + " LD · " + context.logicalNodeCount + " LN · "
+ context.dataAttributeCount + " DA"
: "Open SCL or discover one endpoint"
color: root.theme.muted
font.pixelSize: 8
elide: Text.ElideRight
}
Label {
width: parent.width
text: context.online ? "ONLINE" : "OFFLINE"
color: context.online ? root.theme.green : root.theme.muted
font.pixelSize: 8
font.weight: Font.DemiBold
}
}
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions apps/ied_simulator/test_browser_convergence.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@

workspace_required = [
'component RibbonAction: Button',
'Layout.preferredHeight: 94',
'text: "MODEL"',
'Layout.preferredHeight: 112',
'width: 332',
'text: "COMMANDS"',
'text: "Online"',
'"Discover IED"',
Expand Down
50 changes: 50 additions & 0 deletions apps/ied_simulator/test_browser_ribbon_legibility.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/usr/bin/env python3
"""Browser ribbon legibility and ownership contract (no Qt dependencies).

Guards against regressing to the 7px/8px pseudo-icons seen on field screenshots.
Native Qt CI still renders and starts QML on the exact source commit.
"""
from pathlib import Path

source = (Path(__file__).resolve().parent / "qml/IedBrowserWorkspace.qml").read_text(encoding="utf-8")
ribbon = source.split("component RibbonAction: Button {", 1)[-1].split(
"function watchedRows()", 1)[0]
section = source.split("id: ribbonRow", 1)[-1].split(
"visible: context.authorityKey", 1)[0]
required = (
"implicitWidth: 80",
"implicitHeight: 86",
"width: 37",
"height: 35",
"font.pixelSize: action.badge.length > 2 ? 12 : 16",
"font.pixelSize: 11",
"maximumLineCount: 2",
"wrapMode: Text.WordWrap",
"action.hovered ? root.theme.accentSoft",
"color: action.enabled ? root.theme.text : root.theme.muted",
)
if any(item not in ribbon for item in required):
raise SystemExit("BROWSER_RIBBON_FAIL tiny or unreadable operator action")
if "Layout.preferredHeight: 112" not in source:
raise SystemExit("BROWSER_RIBBON_FAIL toolbar clips 86px actions")
for label in ("APPLICATION", "ENDPOINT", "IED SESSION", "DATA", "SERVICES"):
if f'text: "{label}"' not in section:
raise SystemExit("BROWSER_RIBBON_FAIL missing service group " + label)
if 'text: "MODEL"' in section:
raise SystemExit("BROWSER_RIBBON_FAIL duplicate identity chrome")
for item in (
"onClicked: reports.enableSelected(true)",
"onClicked: reports.disableSelected()",
"session.discoverAndConnect()",
"onClicked: session.disconnectFromIed()",
"onClicked: client.readEngineeringSelected()",
):
if item not in section:
raise SystemExit("BROWSER_RIBBON_FAIL lost live operation " + item)
if "implicitWidth: 66" in ribbon or "font.pixelSize: 8" in ribbon:
raise SystemExit("BROWSER_RIBBON_FAIL retained miniature action")
# Explicit negative regression: an unapproved tiny font must break the gate.
shrunk = ribbon.replace("font.pixelSize: 11", "font.pixelSize: 8", 1)
if "font.pixelSize: 8" not in shrunk:
raise SystemExit("BROWSER_RIBBON_FAIL ineffective tiny-caption regression")
print("BROWSER_RIBBON_PASS legibility=12px_or_larger badges=37px captions=11px actions=80x86")
Loading