From 68503e2a7dc8b890f254dad4a130458a9bd39797 Mon Sep 17 00:00:00 2001 From: ispyisail Date: Tue, 22 Sep 2026 22:36:45 +1200 Subject: [PATCH] checkContinuity: catch cross-folio report-link colour/style mismatches New report_link_mismatch finding (severity "warning", not "error" the way potential_mismatch is): a next_report/previous_report folio-jump pair whose conductors disagree on colour, style, num, or any of the other checked properties. Unlike potential_mismatch, this one is not proof of external tampering -- LinkElementCommand::isLinkable() only ever checks type and freedom (see its own doc comment), never conductor properties, so nothing in QElectroTech copies one side's colour onto the other when a report link is made or keeps them in sync afterwards. This is a real, unenforced gap reachable through completely ordinary use, not a defect a script or the GUI could introduce. Reproduces qelectrotech/qelectrotech-source-mirror#974 exactly: downloaded the reporter's actual project, traced the mismatched wire to a "Folio suivant"/"Folio precedent" link pair, and confirmed via query that the two sides read color="#0000ff" and color="#55aa00" while the link's other four conductors (0V/Low/High/Ground) matched -- ruling out a rendering artifact. Verified fresh with a synthetic reproduction (tests in misc/qet-mcp) using the shipped 02going_arrow.elmt/ 01coming_arrow.elmt pair, giving exactly one finding, not one per folio-link conductor. Also fixes a real gap in the existing potential_mismatch check while here: checked_properties was missing "color" and "style" entirely, checking only "conductor_color" (ConductorProperties::m_wire_color, a separate free-text documentation field, typically empty) -- meaning the same-folio version of this exact bug class would have gone undetected too. Co-Authored-By: Claude Sonnet 5 --- sources/scripting/qetscriptapi.cpp | 81 ++++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 3 deletions(-) diff --git a/sources/scripting/qetscriptapi.cpp b/sources/scripting/qetscriptapi.cpp index c7e659927..adc84d5d5 100644 --- a/sources/scripting/qetscriptapi.cpp +++ b/sources/scripting/qetscriptapi.cpp @@ -3912,10 +3912,18 @@ QVariantList QetScriptApi::checkContinuity(int folioIndex) } } + // "color"/"style" are the rendered pen -- what a person actually sees + // as "this wire is blue" -- distinct from "conductor_color" + // (ConductorProperties::m_wire_color), a separate free-text + // documentation field that is typically empty and says nothing about + // how the wire is drawn. A mismatch check that only covered the + // documentation field would miss exactly the visible kind of + // inconsistency a person would report (issue #974: the two halves of + // one folio-link conductor drawn in different colours). static const QStringList checked_properties = { - QStringLiteral("num"), QStringLiteral("conductor_color"), - QStringLiteral("conductor_section"), QStringLiteral("function"), - QStringLiteral("bus"), QStringLiteral("cable")}; + QStringLiteral("num"), QStringLiteral("color"), QStringLiteral("style"), + QStringLiteral("conductor_color"), QStringLiteral("conductor_section"), + QStringLiteral("function"), QStringLiteral("bus"), QStringLiteral("cable")}; QSet visited; for (int f = 0; f < diagrams.count(); ++f) { @@ -3954,6 +3962,73 @@ QVariantList QetScriptApi::checkContinuity(int folioIndex) } } + // report_link_mismatch (warning, not error): a next_report/ + // previous_report pair -- QElectroTech's folio-jump-arrow links, + // e.g. "Folio suivant"/"Folio précédent" -- is meant to represent one + // wire continuing across a folio boundary, but LinkElementCommand:: + // isLinkable() only ever checks type and freedom (see its own doc + // comment), never conductor properties. Nothing in QElectroTech + // copies one side's colour/style/num onto the other when the link is + // made, or keeps them in sync afterwards, so this is a real, + // unenforced gap rather than something a script or the GUI could + // have broken -- hence "warning", not "error" the way + // potential_mismatch is (which the app's own edits can never + // produce, so any occurrence there is definitely external tampering). + // Terminals are matched by index between the two linked elements: a + // report pair is authored as matching symbols carrying the same set + // of wires in the same declared order, the same convention terminal + // indexing already follows everywhere else in this API. + QSet visited_report; + for (int f = 0; f < diagrams.count(); ++f) { + if (folioIndex >= 0 && f != folioIndex) continue; + DiagramContent content(diagrams.at(f), false); + for (Element *elmt : std::as_const(content.m_elements)) { + if (elmt->linkType() != Element::NextReport + && elmt->linkType() != Element::PreviousReport) continue; + if (visited_report.contains(elmt)) continue; + + const QList linked = elmt->linkedElements(); + for (Element *other : linked) { + if (visited_report.contains(other)) continue; + visited_report << elmt << other; + + const QList ta = elmt->terminals(); + const QList tb = other->terminals(); + const int n = qMin(ta.count(), tb.count()); + auto *other_diagram = qobject_cast(other->scene()); + const int other_folio = other_diagram ? diagrams.indexOf(other_diagram) : -1; + + for (int i = 0; i < n; ++i) { + const QList ca = ta.at(i)->conductors(); + const QList cb = tb.at(i)->conductors(); + // ambiguous (>1) or unconnected (0) on either side: + // nothing to meaningfully compare + if (ca.count() != 1 || cb.count() != 1) continue; + + for (const QString &prop : checked_properties) { + const QString va = conductorPropertyValue(ca.first()->properties(), prop); + const QString vb = conductorPropertyValue(cb.first()->properties(), prop); + if (va == vb) continue; + QVariantMap finding; + finding.insert(QStringLiteral("kind"), QStringLiteral("report_link_mismatch")); + finding.insert(QStringLiteral("severity"), QStringLiteral("warning")); + finding.insert(QStringLiteral("folio"), f); + finding.insert(QStringLiteral("element"), elmt->uuid().toString()); + finding.insert(QStringLiteral("otherFolio"), other_folio); + finding.insert(QStringLiteral("otherElement"), other->uuid().toString()); + finding.insert(QStringLiteral("terminal"), i); + finding.insert(QStringLiteral("property"), prop); + finding.insert(QStringLiteral("values"), QStringList{va, vb}); + finding.insert(QStringLiteral("message"), + QStringLiteral("folio-link conductor disagrees on %1 across the " + "link: '%2' vs '%3'").arg(prop, va, vb)); + findings << finding; + } + } + } + } + } + return findings; }