test(applet): replace covered main.qml greps with executed tests (#312)

* test(applet): replace covered main.qml presentation greps with executed tests

First third of the feature-parity assertions on contents/ui/main.qml
(main-parity-a): 27 removed after experiment, 3 kept with a stated reason.
No production file changes; git diff contents/ is empty.

New executed tests, each shown red against the same mutation first:
- tests/test_provider_cost_presentation.py:
  test_main_cost_wrappers_delegate_to_presentation extracts the seven cost
  wrappers plus spendTotalLine, spendProviderCosts and
  spendHistoryStillBuilding and runs them against CostPresentation.js.
- tests/test_provider_metadata.py: test_normalizeStampsTheLiveReceiptClock
  and test_dashboardSectionsMapThroughDisplayRows.
- tests/test_usage_recovery.py: test_popup_menu_offers_accounts_and_docs
  (PopupMenuRows fixture extracting actionRows and the identity-table URL
  wrappers).
- tests/test_panel_text_recovery.py:
  test_production_menu_bar_text_follows_display_mode (MenuBarText fixture
  extracting menuBarDisplayText and its row pipeline).

Evidence, per removed assertion (mutation -> red test):
- i18n("Tokens unavailable"): "Tokens gone" in spendTotalLine ->
  test_spendTotalLinePrintsSubtotalOrUnavailable.
- function costBreakdownRows(tokenCost): total-tokens lane zeroed (row drops
  out) -> test_breakdownRowsKeepEveryLane.
- function costModelRows(tokenCost): null payload ->
  test_modelAndHistoryRowsFollowPresentation.
- function costHistoryRows(tokenCost): metric flipped ->
  test_modelAndHistoryRowsFollowPresentation.
- function costPeakLine(points): forced "" ->
  test_summaryLinesWordTheSelectedMetric.
- function costAverageDailyLine(points): forced "" -> same test.
- function costPerMillionLine(tokenCost): forced "" -> same test.
- CostPresentation.spendSnapshots(tokenCosts, costHistoryDays: {} input ->
  total/sort/still-building wrapper tests.
- CostPresentation.breakdownRows(: result replaced by raw lane dicts ->
  test_breakdownRowsKeepEveryLane.
- CostPresentation.modelRows(: null payload ->
  test_modelAndHistoryRowsFollowPresentation.
- CostPresentation.historyRows(: metric flipped -> same test.
- CostPresentation.averageDailyValue(: metric flipped ->
  test_summaryLinesWordTheSelectedMetric.
- CostPresentation.perMillionAmount(: null -> same test.
- CostPresentation.spendTotals(: [] input ->
  test_spendTotalLinePrintsSubtotalOrUnavailable.
- CostPresentation.historyStillBuilding(: [] input ->
  test_stillBuildingMirrorsScanCoverage.
- function costChartPoints(points): metric flipped ->
  test_chartPointsFollowTheSelectedMetric.
- ProviderSnapshot.normalize(item, Date.now()): timestamp 0 ->
  test_normalizeStampsTheLiveReceiptClock.
- kpis: dashboard.kpis.map(dashboardDisplayRow): kpis/rows emptied ->
  test_dashboardSectionsMapThroughDisplayRows.
- rows: dashboard.rows.map(dashboardDisplayRow): same test.
- function selectAccount(providerID, accountIdentity): early return ->
  test_account_refresh (AccountRefresh cases; pre-existing, no new test).
- function accountOptionsForProvider(providerID): return [] ->
  test_account_refresh cached cases (pre-existing).
- function providerDocsUrl(providerID): return "" ->
  test_menuOffersAccountsAndDocs.
- function providerLoginUrl(providerID): return "" -> same test.
- action: "docs": renamed to "help" -> same test.
- action: "accounts": renamed to "users" -> same test.
- function menuBarDisplayText(item): return "MUTATED" ->
  test_menuBarTextFollowsDisplayMode.
- function safeMenuBarDisplayMode(value): return "bogus" ->
  test_safeMenuBarDisplayModeFallsBackToPercent.

Kept with reason (breaking mutation stays green):
- CostPresentation.numberFormat(: separators swapped keeps all executed
  modules green. The costNumberFormat property initializer cannot be
  extracted, so fixtures declare the format themselves; unobservable in the
  harness.
- property string menuBarDisplayMode / property bool
  resetTimesShowAbsolute: hardcoding either value keeps every panel test
  green. Both initializers read Plasmoid.configuration, which is absent
  under test; mode routing through these values is covered by MenuBarText.

Before writing tests, a 14-mutation cost batch stayed green across all 14
executed modules naming main.qml, and a 12-mutation batch reddened only
test_account_refresh and test_forecast_clock (isolated to selectAccount and
accountOptionsForProvider).

Verification: make check-feature-parity, make check-shellcheck and the four
touched Python modules are green. check-python-lint is skipped (pyflakes
absent; CI installs it). Full make check left to the orchestrator.

* test(applet): replace covered main.qml provider greps with executed tests

Second third of the feature-parity assertions on contents/ui/main.qml
(main-parity-b): 23 removed after experiment, 13 kept with a stated reason.
No production file changes; git diff contents/ is empty.

New executed tests, each shown red against the same mutation first:
- tests/test_notification_wiring.py:
  NotificationPipelineTests extracts processNotifications,
  dispatchNotificationIntents, sendPlasmaNotification, resetLabel,
  usageResetText, paceEtaText and resetText, and pins the disabled/empty
  guards, prime-then-escalate quota dispatch (critical urgency, committed
  memo stays silent) and status dispatch with mapped urgency.
  notificationUrgency joined the extracted FUNCTIONS with a severity-mapping
  test; the update fixture now compares the exact "Open release page"
  action label.
- tests/test_panel_selection.py: ProviderSelectionTests extracts
  providerIndexForID, overviewProviders, panelProviderItems,
  autoSelectedProviderIndex, updateSelectedProvider,
  selectedCompactProvider, globalViewAvailability and boundedConfigRevision
  against the real OverviewProviders/PanelProviders/ProviderAutoSelect/
  PopupSelection modules.
- tests/test_quota_reset_metadata.py: QuotaMarkerTests extracts
  quotaWarningMarkers, quotaSeverity, quotaMeterColor and statusBadgeColor
  against the real QuotaThresholds module and the real Kirigami Theme
  singleton (compared, not hardcoded).

Evidence, per removed assertion (mutation -> red test):
- function primaryIncidentProvider(): return null ->
  test_notification_adapters_preserve_real_transitions (pre-existing).
- function notificationObservations(): return [] -> same test.
- function quotaNotificationLevel(row): return "MUTATED" -> same test
  (quota-escalation and recovery cases fail; genuine compare failures).
- function notifyAvailableUpdate(version, url, releaseUrl): early return ->
  test_update_notification_adapters_keep_activations_source_scoped.
- function sendPlasmaNotification(...): return "" -> same test.
- function safeReleaseUrl(url): return "" -> same test.
- notificationDispatcher.send(cleanTitle, ...): return "MUTED-NEVER-SENT" ->
  same test.
- Qt.openUrlExternally(releasePageUrl) dropped -> same test.
- function processNotifications(): early return -> new pipeline test.
- NotificationPlanner.transition( call: observations replaced by [] ->
  new pipeline test.
- function dispatchNotificationIntents(intents, observations): early return
  -> new pipeline test.
- function notificationUrgency(severity): return "normal" -> new urgency
  test (plus the pipeline status case).
- i18n("Open release page"): relabelled inside notifyAvailableUpdate ->
  new exact-label compare (was green before the compare existed).
- function providerIndexForID(providerID): return -1 -> new selection test.
- OverviewProviders.visibleItems(providers, overviewProviderIDsRaw):
  argument replaced by "" -> new selection test.
- function panelProviderItems(): return [] -> new selection test.
- function autoSelectedProviderIndex(items): return -1 -> new selection test.
- function updateSelectedProvider(): early return -> new selection test
  (after adding change-requiring cases: uninitialized lands on the global
  view, an unknown stored ID falls back to the first entry).
- function selectedCompactProvider(): return null -> new selection test.
- function boundedConfigRevision(value): return 0 -> new selection test.
- function statusBadgeColor(severity): return "transparent" -> new markers
  test.
- function quotaWarningMarkers(row): return [] -> new markers test.
- showQuotaWarningMarkers (bare): flag check dropped from
  quotaWarningMarkers -> new markers test (hidden case fails).

Kept with reason (breaking mutation stays green across all 16 naming
modules; fixtures declare their own values, so readonly Plasmoid-backed
declarations and declarative handlers are structurally unobservable
without instantiating the applet):
- import QuotaThresholds.js (rename stays green), readonly
  quotaWarningPercent/quotaCriticalPercent (zeroed stays green),
  onQuotaWarningPercentChanged/onQuotaCriticalPercentChanged (no-op stays
  green), enableNotifications/autoSelectProvider/selectedProviderID/
  overviewProviderIDsRaw/notificationMemo defaults (flipped/seeded stays
  green), readonly selectedProviderIndex binding (forced -1 stays green;
  the lookup logic is pinned by the new selection test),
  onOverviewProviderIDsRawChanged (no-op stays green), refreshInterval
  Math.max bound (zeroed stays green).

Verification: make check-feature-parity and make check-shellcheck green;
the three touched Python modules green (7 tests); the full 16-module
naming set green (69 tests). check-python-lint is skipped (pyflakes
absent; CI installs it). Full make check left to the orchestrator.

* test(applet): replace covered main.qml remaining parity greps with executed tests

Last third of the feature-parity assertions on contents/ui/main.qml
(main-parity-c): 5 removed after experiment, 3 kept with a stated reason.
No production file changes; git diff contents/ is empty.

New executed test, shown red against the same mutation first:
- tests/test_panel_selection.py: CompactProvidersTests extracts
  compactProviders, panelProviderItems, panelMeterRows,
  switcherCandidateRows, usageRowForLane, appendUniqueUsageRow,
  providerKey and clamp against the real PanelProviders/PanelRules/
  PanelDisplay/ProviderIdentity/ProviderNormalizer modules, and pins
  that allowed meters keep the provider, a usageAtLeast-99 meters rule
  drops a 43% provider, and a disabled multi-provider flag hides all.

Evidence, per removed assertion (mutation -> red test):
- CostPresentation.peakPoint(points, costHistoryShowsTokens): metric flag
  negated in costPeakLine ->
  test_main_cost_wrappers_delegate_to_presentation (pre-existing).
- function spendHistoryStillBuilding(): forced false ->
  test_stillBuildingMirrorsScanCoverage (pre-existing).
- PanelDisplay.rowForMode(switcherCandidateRows(item), mode,
  panelQuotaLane): lane argument replaced by "secondary" in
  panelDisplayRow -> test_production_menu_bar_text_follows_display_mode
  (pre-existing; hardcoding the mode stays green because the single-row
  fixture carries both metrics, so the lane mutation pins the call).
- PanelRules.matches(panelVisibilityRules.text, row, panelClockMs):
  gated to false in compactTextSegments ->
  test_production_panel_text_keeps_surrendered_content_recoverable and
  test_production_text_distinguishes_automatic_and_explicit_empty_rosters
  (both pre-existing).
- PanelRules.matchesAny(panelVisibilityRules.meters, rows,
  panelClockMs): gated to true in compactProviders -> new
  test_production_compact_providers_honor_visibility_rules.

Kept with reason (breaking mutation stays green across all 16 naming
modules; fixtures declare their own values, so readonly Plasmoid-backed
declarations are structurally unobservable without instantiating the
applet):
- Math.max(1, Math.min(365, ...costHistoryDays...): 365 -> 30 stays
  green; fixtures declare their own day count.
- PanelDisplay.safeLane(...panelQuotaLane) and
  PanelRules.normalizedRules(...panelVisibilityRules): bogus
  replacements stay green; mode routing through these values is covered
  by MenuBarText and the visibility gates by the selection tests.

Verification: make check-feature-parity and make check-shellcheck green;
the three touched Python modules green (7 tests). check-python-lint is
skipped (pyflakes absent; CI installs it). Full make check left to the
orchestrator.

* test(applet): replace covered main.qml security and theme greps with executed tests

The main-security step (24 assertions on contents/ui/main.qml): 22 removed
after experiment, 2 kept with a stated reason. No production file changes;
git diff contents/ is empty.

New executed tests, each shown red against the same mutation first:
- tests/test_notification_wiring.py: test_providerMapKeyNormalizesAliases
  extracts providerMapKey, selectedAccountForProvider,
  markNotificationProvidersFresh and notificationScopeKey from the applet
  surface and pins that the 11labs alias shares the elevenlabs slot
  everywhere; test_availableUpdatePersistsMemoVersion pins the
  lastNotifiedUpdateVersion config write and the no-reannounce memo;
  test_installedUpdateNotifiesOnceWithVersion pins the installed-update
  notification and its disabled guard.
- tests/test_provider_metadata.py: test_dashboardRowBoundsUntrustedText
  (600-char part renders exactly 500), test_hostileStatusUrlFallsBackTo-
  IdentityTable (evil-host status URL falls back, same-host candidate
  passes, unknown provider yields ""), test_providerIconSourceFallsBack-
  ForUnusableKeys (path traversal/markup fall back to view-statistics).
  safeStatusUrl, providerStatusUrl and providerIconSource joined the
  extracted FUNCTIONS and the safeStatusUrl stub was removed.
- tests/test_usage_recovery.py: test_statusActionOpensGuardedUrl extracts
  performAction with the external open routed to a recorder and pins the
  status action opens the guarded URL.
- tests/test_quota_reset_metadata.py: ProviderColorTests extracts
  providerColor, readableAccentColor and providerReadableColor and pins
  brand survival, the highlight fallback and the >= 3 contrast bound
  against the live Kirigami Theme singleton.
- tests/test_sessions_controller.py: AppletRefreshDelegationTests extracts
  refreshSessions with a stub controller and pins the forward-and-return.

Evidence, per removed assertion (mutation -> red test):
- function providerMapKey(providerID): body returns raw id ->
  test_providerMapKeyNormalizesAliases (new; green on pristine, red on
  mutation; pre-existing suite stayed green).
- var providerID = providerMapKey(item.provider): raw id in
  markNotificationProvidersFresh and in notificationScopeKey (each site
  mutated separately) -> same new test.
- var key = providerMapKey(providerID): raw key in
  selectedAccountForProvider -> same new test.
- boundedDisplayText(parts.join, 500): 500 -> 5000 ->
  test_dashboardRowBoundsUntrustedText (new).
- var fileName = providerIconFileName(value): fallback dropped ->
  test_providerIconSourceFallsBackForUnusableKeys (new).
- function safeStatusUrl(providerID, url): guard replaced by
  passthrough-or-fallback -> test_hostileStatusUrlFallsBackToIdentityTable
  (new).
- statusUrl: safeStatusUrl(...): replaced by snapshot.statusUrl ->
  same new test.
- Qt.openUrlExternally(safeStatusUrl(...)): replaced by item.statusUrl ->
  test_statusActionOpensGuardedUrl (new).
- function safeReleaseUrl(url): guard replaced by trim passthrough ->
  test_updatesWithoutATrustedReleaseUrlStayNonActionable (pre-existing).
- Qt.openUrlExternally(releasePageUrl): line dropped ->
  test_queuedActionableUpdatesOpenTheirOwnReleasePage (pre-existing).
- httpsUrlHost === github.com: host swapped to example.org ->
  both update-wiring tests (pre-existing).
- nextPending[sourceName] = releasePageUrl: registration dropped ->
  both update-wiring tests (pre-existing).
- delete nextPending[sourceName]: retirement dropped -> both
  update-wiring tests (pre-existing).
- function providerColor(value): body returns highlightColor ->
  test_brandColorsSurviveAndUnknownFallsBackToHighlight (new).
- function providerReadableColor(value, background): body returns the
  background -> test_readableColorKeepsContrastOnThemeBackground (new).
- Kirigami.Theme.textColor: badge unknown branch -> transparent ->
  test_badgeColoursFollowSeverity (pre-existing).
- Kirigami.Theme.highlightColor: providerColor fallback -> red ->
  both new ProviderColors tests.
- Kirigami.Theme.negativeTextColor: badge critical branch ->
  transparent -> test_badgeColoursFollowSeverity and
  test_meterFillUsesTheBadgeColourPastTheWarningStep (pre-existing).
- Kirigami.Theme.neutralTextColor: badge minor branch -> transparent ->
  test_badgeColoursFollowSeverity (pre-existing).
- notifyInstalledUpdate(version): early return ->
  test_installedUpdateNotifiesOnceWithVersion (new).
- Plasmoid.configuration.lastNotifiedUpdateVersion = memoKey: write
  dropped -> test_availableUpdatePersistsMemoVersion (new).
- function refreshSessions(): body returns false ->
  test_refreshSessionsDelegatesToController (new).

Kept with reason (breaking mutation stays green; fixtures declare their
own state, so applet-level declarations are structurally unobservable
without instantiating the applet):
- property var pendingUpdateReleaseUrls: ({}): deleting it keeps the
  whole updater suite green.
- import ThemeContrast.js as ThemeContrast: removing it keeps all six
  naming modules green.

Verification: make check-process-lifecycle, check-security-regressions,
check-theme-boundaries, check-update-widget and check-shellcheck green;
the five touched Python modules green (incl. the 80s sessions-controller
lifecycle run). check-python-lint is skipped (pyflakes absent; CI installs
it). Full make check left to the orchestrator.

* test(applet): replace covered main.qml provider greps with executed tests

Second third of the feature-parity assertions on contents/ui/main.qml
(main-parity-b): 4 removed after experiment, 19 kept with a stated
reason. No production file changes; git diff contents/ is empty.

New executed test, shown red against each mutation first:
- tests/test_provider_cost_presentation.py:
  test_cost_metric_helpers_sanitize_before_persisting extracts
  safeCostHistoryMetric and setCostHistoryMetric from the applet surface
  (Plasmoid.configuration rewritten to a stub, the established pattern)
  and pins that only "tokens"/"cost" survive, so the chart and the
  summary lines can never disagree.

Evidence, per removed assertion (mutation -> red test):
- property bool showQuotaWarningMarkers: flag check dropped from
  quotaWarningMarkers -> test_markersFollowTheVisibilityFlag
  (test_quota_reset_metadata.py; pre-existing).
- function safeCostHistoryMetric(value): body forced to return "cost" ->
  new sanitize test (tokens case fails).
- function setCostHistoryMetric(metric): raw assign without the safe call
  -> new sanitize test (bogus case persists unfiltered).
- CostPresentation.chartPoints(costNumberFormat, points,
  costHistoryShowsTokens): flag negated in costChartPoints ->
  test_chartPointsFollowTheSelectedMetric (pre-existing).

Kept with reason (breaking mutation stays green across all 17 Python
naming modules; fixtures declare their own values, so readonly
Plasmoid-backed declarations and declarative handlers are structurally
unobservable without instantiating the applet):
- import QuotaThresholds.js (alias renamed stays green), readonly
  quotaWarningPercent/quotaCriticalPercent (zeroed stays green),
  onQuotaWarningPercentChanged/onQuotaCriticalPercentChanged (no-op stays
  green).
- enableNotifications/autoSelectProvider (flipped stays green),
  selectedProviderID/overviewProviderIDsRaw/notificationMemo (seeded
  stays green), readonly selectedProviderIndex binding (forced -1 stays
  green; the lookup logic is pinned by the selection tests),
  onOverviewProviderIDsRawChanged (no-op stays green), refreshInterval
  Math.max bound (zeroed stays green).
- costUsageEnabled (flipped stays green; the controller's own copy stays
  pinned by test_startupRunsWhileHiddenAndReentryKeepsFreshData),
  costHistoryDays fallback (30 -> 7 stays green), costHistoryMetric
  initializer (forced "cost" stays green; sanitization behind it is pinned
  by the new test), 365-day clamp (365 -> 30 stays green).
- PanelDisplay.safeLane(...panelQuotaLane) ("bogus" stays green) and
  PanelRules.normalizedRules(...panelVisibilityRules) (({}) stays
  green); routing through these values is covered by the menu-bar and
  compact provider selection tests.

Verification: make check-feature-parity and make check-shellcheck green;
the touched Python module green (3 tests); all 17 naming modules green
under the combined breaking mutations. check-python-lint is skipped
(pyflakes absent; CI installs it). Full make check left to the
orchestrator.

* test(applet): replace covered main.qml presentation greps with executed tests

The feature-parity assertions on contents/ui/main.qml (main-parity-a):
8 removed after experiment, 25 kept with a stated reason.
No production file changes; git diff contents/ is empty.

New executed tests, each shown red against the same mutation first:
- tests/test_panel_text_recovery.py: MenuBarText gains runOutItem (a
  pace-clocked row: paceOnTop false, paceEtaSeconds 7200 observed at the
  live panel clock) asserting runOut mode renders "2 hours".
- tests/test_notification_wiring.py: NotificationPipeline gains
  test_quotaNotificationBodyCarriesTheResetLine, priming at 85% then
  escalating to 96% with a +2h resetsAt row and asserting the quota body
  carries both "96" and the "Resets" prefix.

Evidence, per removed assertion (mutation -> red test):
- PanelDisplay.rowForMode(: lane replaced by "secondary" in
  panelDisplayRow -> test_production_menu_bar_text_follows_display_mode
  (pre-existing).
- mode === "runOut": mutated to "runOutX" -> new runOutItem case
  (was green before: the old single-row fixture yields "" on both the
  run-out branch and the percent fallback, so nothing pinned the branch).
- if (!paceWarningActive(row)) {: guard negated in runOutTextForRow ->
  panel-text, notification-wiring and forecast-clock suites
  (2 failures, pre-existing).
- PanelDisplay.remainingSeconds(: call replaced by paceEtaText(0) ->
  test_cached_account_forecast_uses_its_own_receipt_time
  (test_forecast_clock.py; pre-existing).
- function resetText(window, absolute): forced "MUTATED" ->
  test_translated_resets_follow_local_time_and_live_clock
  (test_reset_presentation.py; pre-existing).
- function usageResetText(row): forced "MUTATED" -> same test.
- ResetPresentation.labelParts(value): argument replaced by "" -> same
  test (qmltestrunner compare failures in all six locales).
- var resetLine = resetLabel(usageResetText(row)): resetLabel wrapper
  dropped -> new test_quotaNotificationBodyCarriesTheResetLine
  (was green before: pipeline quota fixtures carried no reset metadata).

Kept with reason (23 breaking mutations applied at once stay green
across all 18 Python modules naming main.qml, 75 tests OK; fixtures
declare their own values, so Plasmoid-backed declarations and the
declarative Timer wiring are structurally unobservable without
instantiating the applet):
- CostPresentation.numberFormat(: separators swapped stays green;
  fixtures declare their own format.
- property string menuBarDisplayMode / Plasmoid.configuration.
  menuBarDisplayMode: forced "pace" stays green; mode routing is
  covered by MenuBarText.
- property bool resetTimesShowAbsolute: flipped stays green; covered
  indirectly through the reset adapters.
- id: panelClockTimer (dropped) and root.panelClockMs = Date.now()
  (frozen to 0): both stay green; the minute-timer wiring cannot run
  without the applet instance.
- import QuotaThresholds.js (alias renamed), readonly
  quotaWarningPercent/quotaCriticalPercent (zeroed),
  onQuotaWarningPercentChanged/onQuotaCriticalPercentChanged (no-op).
- enableNotifications/autoSelectProvider (flipped),
  selectedProviderID/overviewProviderIDsRaw/notificationMemo (seeded),
  readonly selectedProviderIndex (forced -1; the lookup itself is
  pinned by the panel-selection tests),
  onOverviewProviderIDsRawChanged (no-op), refreshInterval Math.max
  bound (zeroed).
- costUsageEnabled (flipped), costHistoryDays fallback (30 -> 7),
  costHistoryMetric initializer (forced "cost"; sanitization behind it
  is pinned by test_cost_metric_helpers_sanitize_before_persisting),
  365-day clamp (365 -> 30).
- PanelDisplay.safeLane(...panelQuotaLane) ("bogus") and
  PanelRules.normalizedRules(...panelVisibilityRules) (({})); routing
  through these values is covered by MenuBarText and the compact
  provider selection tests.

Verification: make check-feature-parity and make check-shellcheck
green; the two touched Python modules green (5 tests); all 18 naming
modules green under the combined keep-mutations. check-python-lint
not run (pyflakes absent on this machine; CI installs it). Full make
check left to the orchestrator.

* test(applet): pin installed-update forwarding and all menu-bar modes

1. tests/test_notification_wiring.py: new UpdateInstalledForwardingTests
extracts the onUpdateInstalled handler body from main.qml and executes it
against a stub recording root.notifyInstalledUpdate calls (no source-text
assertion). Evidence:
- pristine tree: new test green.
- line 2733 'root.notifyInstalledUpdate(version)' deleted: new test red
  (qmltestrunner: 1 failed), while test_notification_wiring
  UpdateNotificationWiringTests and test_widget_update_controller stay
  green (6 tests OK), confirming the dropped protection is real.
- argument dropped ('root.notifyInstalledUpdate()'): new test red.

2. tests/test_panel_text_recovery.py: the safeMenuBarDisplayMode QML
checks are generated in Python from contents/ui/PanelDisplay.js
('var <name>Mode = "<value>"', yielding percent/pace/both/resetTime/
runOut) instead of the hardcoded 'pace' survival line. Evidence:
- pristine tree: suite green (2 tests OK).
- safeMode allowlist with runOut rejected: suite red (1 failure),
  where the old pace-only checks stayed green.

Verification: make check-update-widget green; the two touched Python
modules green (6 tests OK). check-python-lint skipped (pyflakes absent).
No production changes; git diff contents/ is empty.
bf944063b7Simone committed on 9/21/2026, 11:33:49 PM· committed by GitHubparent281014c
13 files changedLine totals unavailable