Skip to content

chore: delete the stream-start leftover-frame guard now that Core owns it - #810

Open
tylerkron wants to merge 4 commits into
mainfrom
chore/delete-leftover-frame-guard
Open

chore: delete the stream-start leftover-frame guard now that Core owns it#810
tylerkron wants to merge 4 commits into
mainfrom
chore/delete-leftover-frame-guard

Conversation

@tylerkron

Copy link
Copy Markdown
Contributor

Deletes the desktop's stream-start leftover-frame guard (issue #573) now that Core v1.4.0 owns it, and replaces it with a subscription to Core's StreamFrameDiscarded for diagnostics.

Closes #679

Why this is no longer blocked on a product decision

The issue body says this is blocked on setting a supported firmware floor at >= 3.6.0, because the guard still protects users on older firmware, and records that Core would not take a host-side guard (daqifi-core#246, closed won't-implement). Core reversed that. daqifi-core#425/#428 shipped in v1.4.0 as the internal StreamFrameGate, a faithful port of ours:

Desktop (deleted here) Core v1.4.0 StreamFrameGate
STALE_FRAME_WINDOW_SECONDS = 2.5 LeftoverWindowSamplePeriods = 2.5
MAX_DISCARDED_LEFTOVER_FRAMES = 5 MaxDiscardedFrames = 5
_lastSeenDeviceTimestamp / _hasSeenDeviceTimestamp _lastSeenDeviceTimestamp / _hasDeviceTimestampReference
IsLeftoverFrameFromPreviousSession IsLeftoverFromPreviousSession
arming block in InitializeStreaming BeginSession(timestampFrequency, streamingFrequencyHz)

So users on firmware <= 3.5.0 keep the protection — with a tighter window — and no floor is needed.

Core's is the better implementation. Ours hardcoded a 2.5 second window. Core's is 2.5 sample periods, computed per session from the actual streaming frequency (2.5 * ticksPerSecond / rate). At 100 Hz ours was 100x wider than it needed to be — exactly the margin in which a genuine early frame could be misclassified as a leftover. Core also screens the raw-frame path, so a withheld frame never reaches StreamMessageReceived at all.

What changed

Deleted (AbstractStreamingDevice.cs, −164 lines against +86):

  • constants STALE_FRAME_WINDOW_SECONDS, MAX_DISCARDED_LEFTOVER_FRAMES
  • fields _lastSeenDeviceTimestamp, _hasSeenDeviceTimestamp, _checkForLeftoverFrames, _discardedLeftoverFrameCount, _pendingFirstFrameValidation, _heldFirstFrame
  • methods TrackLastSeenDeviceTimestamp, IsLeftoverFrameFromPreviousSession, ValidateFirstFramesWithoutReference
  • the arming block in InitializeStreaming, both guard branches in OnStreamMessageReceived
  • Daqifi.Desktop.Test/Device/StreamStartLeftoverFrameTests.cs (373 lines)

Added: OnCoreStreamFrameDiscarded, wired into the existing SubscribeCoreDeviceEvents / UnsubscribeCoreDeviceEvents pair, plus StreamFrameDiscardDiagnosticsTests.cs.

Bench evidence

Not re-benched here (per the hardware coordination policy, and the issue's suggestion to verify against firmware <= 3.5.0 is explicitly declined — this bench hardware has known brick-recovery problems, firmware#568). It does not need to be: Core 1.4.0's gate already fired unprompted on this exact hardware (Nyquist on COM3) during PR #806's bench pass:

DISCARD [PartialAnalogFrame]  ts=2806686261  analog=1/4   <- at stream start
DISCARD [StaleLeftoverFrame]  ts=3473519994  analog=1/4   <- at stream restart

The second is precisely the #573 scenario this guard was written for, confirmed on real hardware. Core's own unit coverage plus the end-to-end tests below cover the rest.

Three judgment calls

1. Log level: debug breadcrumb, never Error. Discards are expected on affected firmware — one PartialAnalogFrame per stream start up to 3.7.2, one StaleLeftoverFrame per restart. AppLogger.Error is the only path that captures to Sentry, so routing discards there would file an issue per streaming session for a device-side condition no app change can fix. This repo has hit that exact flood three separate times (#775, #779, #801). A breadcrumb keeps every discard on the timeline of any error that does get captured, which is where it is actually useful, and DiscardedFrame_IsRecordedAsBreadcrumb_AndNeverAsError is the tripwire.

2. Nothing outside the guard read the deleted state. Grepped every reader of all six fields before deleting. _hasSeenDeviceTimestamp (which gated the arming decision) and _lastSeenDeviceTimestamp were read only by IsLeftoverFrameFromPreviousSession, ValidateFirstFramesWithoutReference, and the InitializeStreaming arming block — all deleted. TrackLastSeenDeviceTimestamp's call from the !IsStreaming early-return had a real purpose (the device can emit a final frame after the stop lands, and the next session's latched frame follows that one); Core covers it — StreamFrameGate.TrackFrame runs for frames outside a session too.

3. Core arms BeginSession itself; the desktop just must not reorder. DaqifiStreamingDevice.StartStreamingBeginStreamingSession_frameGate.BeginSession(TimestampFrequency, StreamingFrequency). The desktop needs no new call — but InitializeStreaming's existing coreStreamingDevice.StreamingFrequency = StreamingFrequency assignment before StartStreaming is now load-bearing beyond commanding the rate, since the window is sized from that value. Commented in place so it does not get moved.

One thing the deletion also fixes

OnCoreStreamFrameDiscarded closes _acceptChannelSamples. A frame Core withholds raises no StreamMessageReceived, so OnStreamMessageReceived never runs for it and cannot close the gate — yet Core still decodes a PartialAnalogFrame for its digital payload. Without closing it, those samples reach desktop channels stamped with _currentFrameTimestamp from whichever frame was accepted before them, possibly from the previous session, and DataSample is what gets persisted. DiscardedFrame_ClosesTheChannelSampleGate was mutation-checked: removing the single line makes it fail.

Behavior difference worth naming

Core deliberately does not port ValidateFirstFramesWithoutReference — the held-first-frame validation for the very first session after connect, where there is no counter reference. Core documents this as a known limitation and rejects the hold on the grounds that it delays every consumer's first sample to defend against a frame never observed on current firmware. Every session from the second onward — the stop/start case #573 actually describes — is covered. Accepted; the issue's plan calls for deleting that method.

Testing

  • dotnet build -tl:on — clean, zero new warnings (the 6 remaining >120-char lines in the file are all pre-existing; the deletions removed 3 of 9).
  • dotnet test --filter "TestCategory!=Ui&FullyQualifiedName!~WindowsFirewallWrapperTests" -tl:on864 passing, 0 failing. Baseline on the parent branch is 868; 868 − 10 deleted + 6 new = 864.

The 6 new tests drive Core's real StreamFrameGate end to end through the desktop wrapper rather than mocking the event, because the deletion is only safe if the protection genuinely survives it:

  • leftover frame at restart is never dispatched, and the session anchors on genuine data
  • same across the ~86 s counter wrap (the negative-time symptom)
  • a genuine first frame beyond the window is processed immediately (no over-discarding)
  • the discard is a debug breadcrumb, never Error or Warning
  • the discard closes the channel-sample gate
  • UnsubscribeCoreDeviceEvents detaches the discard handler with the rest (asserted against Core's own DiscardedStreamFrameCount, so it cannot pass by simply not discarding)

Docs touched: docs/architecture.md's "one frame, one timestamp" note, and the stale comments in FrameTimestampSourceTests / StreamRestartTests that described the desktop-side hold.

Stacked PR

Stacked on #808. #806 already merged; this branch is rebased onto it.

🤖 Generated with Claude Code

@tylerkron
tylerkron requested a review from a team as a code owner August 3, 2026 18:06
@qodo-code-review

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

Remove desktop leftover-frame guard; rely on Core StreamFrameGate diagnostics

✨ Enhancement 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Delete desktop stream-start leftover-frame/first-frame gating now handled by Core v1.4.0.
• Subscribe to Core StreamFrameDiscarded to close the sample gate and add debug breadcrumbs.
• Replace leftover-frame unit tests with end-to-end Core gate diagnostics tests and docs updates.
Diagram

graph TD
  dev{{"DAQ device"}} --> core["Core DaqifiStreamingDevice"] --> gate{"StreamFrameGate"}
  gate -- "accept" --> recv["StreamMessageReceived"] --> desk["AbstractStreamingDevice"] --> proc["ProcessStreamMessage"] --> open["Open sample gate"] --> sample["OnCoreChannelSampleReceived"]
  gate -- "discard" --> disc["StreamFrameDiscarded"] --> handler["OnCoreStreamFrameDiscarded"] --> crumb["Debug breadcrumb"] --> sentry{{"Sentry timeline"}}
  handler --> close["Close sample gate"] --> sample
  subgraph Legend
    direction LR
    _ext{{"External"}} ~~~ _proc["Process"] ~~~ _dec{"Decision"}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Keep desktop guard as a fallback behind Core version check
  • ➕ Extra protection if a deployment accidentally runs with Core < 1.4.0
  • ➕ Lets desktop enforce policy independent of Core behavior changes
  • ➖ Duplicates logic in a hot path and risks divergence
  • ➖ Reintroduces the old fixed-seconds window and its false-positive risk
2. Emit structured metrics/telemetry instead of breadcrumbs
  • ➕ Easier to aggregate discard rates across sessions and devices
  • ➕ Less dependent on log text parsing
  • ➖ Requires new telemetry plumbing and product decisions
  • ➖ Breadcrumbs already provide timeline context with minimal noise

Recommendation: Proceed with this PR’s approach: Core is now the single owner of stream-start frame screening, eliminating duplicated heuristics and reducing false positives via the sample-period window. The desktop’s remaining responsibility—closing the per-channel sample gate and recording a low-noise diagnostic signal via breadcrumbs—is the minimal, correct integration point. A fallback guard is only worth considering if the app must support Core versions prior to v1.4.0.

Files changed (5) +453 / -176

Refactor (1) +86 / -164
AbstractStreamingDevice.csRemove desktop leftover-frame gate; handle Core StreamFrameDiscarded for diagnostics +86/-164

Remove desktop leftover-frame gate; handle Core StreamFrameDiscarded for diagnostics

• Deletes the desktop’s stream-start leftover-frame and first-frame validation logic and all related state. Subscribes to Core’s StreamFrameDiscarded event to (1) close _acceptChannelSamples for frames the desktop never sees and (2) record a debug breadcrumb for discard diagnostics without Sentry error spam. Also updates streaming initialization commentary and documentation to reflect Core-owned screening behavior.

Daqifi.Desktop/Device/AbstractStreamingDevice.cs

Tests (3) +365 / -11
FrameTimestampSourceTests.csUpdate timestamp-source test assumptions now that Core withholds leftovers +8/-9

Update timestamp-source test assumptions now that Core withholds leftovers

• Adjusts comments and assertions to reflect that stream-start leftover frames no longer reach the desktop path because Core’s StreamFrameGate withholds them. Removes language about the desktop holding/validating first frames and updates a precondition message accordingly.

Daqifi.Desktop.Test/Device/FrameTimestampSourceTests.cs

StreamFrameDiscardDiagnosticsTests.csAdd end-to-end tests for Core discard events and desktop diagnostics +354/-0

Add end-to-end tests for Core discard events and desktop diagnostics

• Introduces a new test suite that drives real Core stream-frame gating through a test device wrapper. Verifies stale leftover frames (including counter wrap) never dispatch, genuine restarts process immediately, discards become debug breadcrumbs (not errors), discards close the channel sample gate, and unsubscribing stops diagnostics from firing.

Daqifi.Desktop.Test/Device/StreamFrameDiscardDiagnosticsTests.cs

StreamRestartTests.csClarify UI test stimulus relative to Core’s sample-period discard window +3/-2

Clarify UI test stimulus relative to Core’s sample-period discard window

• Updates the explanatory comment to reference Core’s 2.5-sample-period window (and the retired desktop 2.5-second window) to justify the stop/start gap used in the UI test.

Daqifi.Desktop.UITest/StreamRestartTests.cs

Documentation (1) +2 / -1
architecture.mdDocument Core-owned bad-frame screening and desktop discard handling +2/-1

Document Core-owned bad-frame screening and desktop discard handling

• Updates the streaming flow notes to reflect that Core v1.4.0 withholds stale/partial frames from StreamMessageReceived and reports them via StreamFrameDiscarded. Documents why the desktop subscribes (breadcrumbs + closing the sample gate) despite not receiving the frame itself.

docs/architecture.md

@qodo-code-review

qodo-code-review Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 49 rules

Grey Divider


Remediation recommended

1. DateTime.Now in tests ✓ Resolved 📘 Rule violation ▣ Testability
Description
The new unit tests use DateTime.Now, which makes them depend on the system clock and can introduce
non-determinism across environments. Unit tests should use fixed timestamps or a mocked clock
instead of reading real time.
Code

Daqifi.Desktop.Test/Device/StreamFrameDiscardDiagnosticsTests.cs[190]

+        coreChannel.SetActiveSample(1.25, DateTime.Now);
Relevance

●● Moderate

No prior reviews found about DateTime.Now in tests; team discusses mocking external deps but
outcomes mostly undetermined.

PR-#583
PR-#525

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 244803 requires mocking or controlling external dependencies in unit tests,
including the system clock. The added test uses DateTime.Now when setting samples, which directly
reads the real clock during the test run.

Rule 244803: Mock external dependencies in unit tests
Daqifi.Desktop.Test/Device/StreamFrameDiscardDiagnosticsTests.cs[188-204]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
New unit tests call `DateTime.Now`, introducing an external dependency on the system clock.

## Issue Context
Per compliance, unit tests should not rely on real-time (system clock). Use a fixed `DateTime` value (e.g., `DateTime.UnixEpoch` or a hard-coded UTC timestamp) so tests are deterministic.

## Fix Focus Areas
- Daqifi.Desktop.Test/Device/StreamFrameDiscardDiagnosticsTests.cs[188-204]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread Daqifi.Desktop.Test/Device/StreamFrameDiscardDiagnosticsTests.cs Outdated
@tylerkron
tylerkron force-pushed the chore/delete-leftover-frame-guard branch from d148279 to fb5b4aa Compare August 3, 2026 18:11
claude added 2 commits August 3, 2026 14:59
…owns them

daqifi-core#398 shipped in Core v1.4.0, so the four desktop workarounds that
stood in for it (#776, #779, #780, #782) can go.

1. Timestamp reset (gap 3). Core's ResetAll() now clears session baselines
   only and keeps per-device frequencies, so the desktop's
   _timestampFrequencyApplied gate and the _timestampProcessorSync lock that
   kept the gate and the frequency changing together are both gone. The apply
   is now once per device, keyed on Core's own HasTimestampFrequency. Bonus
   from the same Core release: TimestampResult.UsedFallbackTickPeriod makes
   the previously silent 50 MHz fallback observable, so a device that reports
   no clock now logs one Warning per device instead of nothing at all.

2. Firmware classifier (gap 4). Core added
   FirmwareUpdateState.ReconnectingAfterFlash, emitted only by
   WifiModuleUpdater after the WINC success marker. Verifying is now
   unambiguously the PIC32 flash CRC check, so the FirmwareFlashPhase enum and
   the phase parameter threaded through the coordinator and DaqifiViewModel
   are deleted and the classifier keys on FailedState alone.

   Note the issue text is wrong that this "reduces to keying on
   ReconnectingAfterFlash": PIC32's benign post-flash state is still
   JumpingToApp, which Core did not change. Both arms remain; what the split
   bought is that they are disjoint across the two flows, which is what makes
   the phase parameter unnecessary.

3. SD transfer stalls (gap 1). SdCardDownloadStalledException is deleted and
   the classifier keys on Core's SdCardTransferStalledException.Reason:
   TransferTimeout and TransportClosed stop a batch, NoDataReceived does not
   (the old IsProlongedFailure binary, with strictly more information).

   Core still throws a bare TimeoutException from its own hard download
   deadline, so the scoped normalization at the download call site stays - it
   now produces Core's type instead of ours. Deleting it outright would have
   put a wedged card back on the Sentry arm, which is the whole point of #779.

4. Empty transfers (gap 2). Core discriminates a wedged SD subsystem from a
   legitimately 0-byte file using the directory listing's reported size, so
   the importer's own "0 bytes on disk" throw is deleted - a listed 0-byte
   file now imports as an empty session rather than failing and burning a slot
   in an Import All batch (#780). The separate null-FilePath guard is kept but
   reclassified as a contract violation (Error/Sentry), because Core's
   temp-file overload always sets FilePath.

Tests: TimestampProcessorSerializationTests is deleted along with the lock it
tested, and replaced by StreamTimestampReconstructionTests, which asserts the
user-visible outcomes that survive (a stop/start leaves the session on the
device clock; a device with no reported clock warns once and never at Error).
The SD and firmware classifier suites keep their log-level assertions.

Closes #803

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
WarnOnceAboutFallbackTickPeriod takes a deviceId but suppressed on a
wrapper-wide bool, so the signature promised per-device and the implementation
delivered per-wrapper.

That gap is reachable: discovery reuses the SerialStreamingDevice already
registered for a COM port rather than constructing a new one, so swapping units
on that port leaves one instance decoding a different device's frames. The
second unit would lose the diagnostic entirely.

Key on the frame's own serial via Interlocked.Exchange, which makes the claim
and the already-warned test a single atomic step so racing frames emit once.

Also separates Act from Assert in the firmware classifier tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@tylerkron
tylerkron force-pushed the chore/retire-core-398-bandaids branch from 60d65af to 95bdafc Compare August 3, 2026 21:02
claude added 2 commits August 3, 2026 15:04
…s it

Core v1.4.0 took over the host-side guard the desktop had been carrying since
issue #573 (daqifi-core#425/#428, internal StreamFrameGate), so the desktop's
copy is now dead weight sitting on the hot streaming path.

Deleted from AbstractStreamingDevice:

- constants STALE_FRAME_WINDOW_SECONDS / MAX_DISCARDED_LEFTOVER_FRAMES
- fields _lastSeenDeviceTimestamp, _hasSeenDeviceTimestamp,
  _checkForLeftoverFrames, _discardedLeftoverFrameCount,
  _pendingFirstFrameValidation, _heldFirstFrame
- methods TrackLastSeenDeviceTimestamp, IsLeftoverFrameFromPreviousSession,
  ValidateFirstFramesWithoutReference
- the arming block in InitializeStreaming and both guard branches in
  OnStreamMessageReceived
- StreamStartLeftoverFrameTests

Core's version is strictly better: its detection window is 2.5 sample periods
computed per session from the streaming frequency, where ours was a fixed
2.5 seconds - 100x wider than needed at 100 Hz, which is exactly the margin in
which a genuine early frame could be misclassified as a leftover. Core also
screens the raw-frame path, so a withheld frame no longer reaches
StreamMessageReceived at all.

In its place the desktop subscribes to Core's StreamFrameDiscarded and records
each discard as a debug Sentry breadcrumb - the diagnostic signal
_discardedLeftoverFrameCount used to provide. Deliberately not Error: discards
are EXPECTED on affected firmware (one PartialAnalogFrame per stream start up
to 3.7.2, one StaleLeftoverFrame per restart), and Error is the only path that
captures to Sentry, so it would file an issue per streaming session for a
device-side condition no app change can fix - the flood this repo has already
hit three times (#775, #779, #801).

The handler also closes _acceptChannelSamples. A withheld frame raises no
StreamMessageReceived, so OnStreamMessageReceived cannot close the gate for it,
yet Core still decodes a PartialAnalogFrame for its digital payload - which
would otherwise reach desktop channels stamped with the previously accepted
frame's timestamp. Covered by a test that fails when the line is removed.

InitializeStreaming still assigns Core's StreamingFrequency before
StartStreaming, which is now load-bearing beyond commanding the device's rate:
StartStreaming is what arms the gate, and the window is sized from that value.

Closes #679

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… tests

The two SetActiveSample calls used DateTime.Now for a value the assertions never
inspect - they check whether a sample reaches the wrapper at all, not when. A
fixed stamp keeps the tests deterministic across environments.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@tylerkron
tylerkron force-pushed the chore/delete-leftover-frame-guard branch from fb5b4aa to 2b84029 Compare August 3, 2026 21:07
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

📊 Code Coverage Report

Summary

Summary
Generated on: 8/3/2026 - 9:11:11 PM
Coverage date: 8/3/2026 - 9:09:27 PM - 8/3/2026 - 9:11:06 PM
Parser: MultiReport (2x Cobertura)
Assemblies: 2
Classes: 141
Files: 164
Line coverage: 57.3% (5973 of 10408)
Covered lines: 5973
Uncovered lines: 4435
Coverable lines: 10408
Total lines: 34720
Branch coverage: 43.3% (1594 of 3675)
Covered branches: 1594
Total branches: 3675
Method coverage: Feature is only available for sponsors

Coverage

DAQiFi - 57.5%
Name Line Branch
DAQiFi 57.5% 43.4%
Daqifi.Desktop.App 2.9% 0%
Daqifi.Desktop.Channel.AbstractChannel 74.7% 57.1%
Daqifi.Desktop.Channel.AnalogChannel 46.8% 25%
Daqifi.Desktop.Channel.ChannelColorManager 100% 100%
Daqifi.Desktop.Channel.DataSample 91.6%
Daqifi.Desktop.Channel.DigitalChannel 86.5% 61.1%
Daqifi.Desktop.Configuration.FirewallConfiguration 90.6% 66.6%
Daqifi.Desktop.Configuration.WindowsFirewallWrapper 63% 64.2%
Daqifi.Desktop.ConnectionManager 81.2% 75%
Daqifi.Desktop.Converters.BoolAndToVisibilityConverter 100% 100%
Daqifi.Desktop.Converters.BrushColorMatchConverter 100% 100%
Daqifi.Desktop.Converters.ConnectionTypeToColorConverter 88.8% 83.3%
Daqifi.Desktop.Converters.InvertedBoolToVisibilityConverter 100% 100%
Daqifi.Desktop.Converters.ListToStringConverter 100% 94.4%
Daqifi.Desktop.Converters.NotNullToVisibilityConverter 100% 100%
Daqifi.Desktop.Converters.OxyColorToBrushConverter 100% 100%
Daqifi.Desktop.Device.AbstractStreamingDevice 69% 61.2%
Daqifi.Desktop.Device.ConnectionLostEventArgs 100%
Daqifi.Desktop.Device.DeviceMessage 92.8%
Daqifi.Desktop.Device.Firmware.BootloaderDiscoveredEventArgs 100% 50%
Daqifi.Desktop.Device.Firmware.BootloaderHoldDroppedEventArgs 100% 50%
Daqifi.Desktop.Device.Firmware.BootloaderHoldService 82.7% 83.3%
Daqifi.Desktop.Device.Firmware.BootloaderSessionStreamingDeviceAdapter 10% 8.3%
Daqifi.Desktop.Device.Firmware.BootloaderWatcher 90.7% 72.6%
Daqifi.Desktop.Device.Firmware.FirmwareFailureClassifier 100% 100%
Daqifi.Desktop.Device.Firmware.FirmwareUpdateCoordinator 74.1% 63.4%
Daqifi.Desktop.Device.Firmware.FirmwareUpdateServiceConfig 100%
Daqifi.Desktop.Device.Firmware.HeldBootloader 100% 50%
Daqifi.Desktop.Device.Firmware.HidBootloaderDiscovery 0% 0%
Daqifi.Desktop.Device.NativeMethods 100%
Daqifi.Desktop.Device.SerialDevice.SerialStreamingDevice 56.5% 55.4%
Daqifi.Desktop.Device.WiFiDevice.DaqifiStreamingDevice 59.2% 52.5%
Daqifi.Desktop.DialogService.DialogService 0% 0%
Daqifi.Desktop.DialogService.ServiceLocator 0% 0%
Daqifi.Desktop.DiskSpace.DiskSpaceCheckResult 100%
Daqifi.Desktop.DiskSpace.DiskSpaceEventArgs 100%
Daqifi.Desktop.DiskSpace.DiskSpaceMonitor 88.2% 86.6%
Daqifi.Desktop.DiskSpace.DiskSpaceMonitorCoordinator 100% 100%
Daqifi.Desktop.DiskSpace.DiskSpaceStartDecision 100%
Daqifi.Desktop.DuplicateDeviceCheckResult 100%
Daqifi.Desktop.Exporter.LoggingSessionSampleSource 96.3% 75%
Daqifi.Desktop.Exporter.OptimizedLoggingSessionExporter 68.9% 54.5%
Daqifi.Desktop.Helpers.BooleanConverter`1 100% 100%
Daqifi.Desktop.Helpers.BooleanToInverseBoolConverter 100% 100%
Daqifi.Desktop.Helpers.BooleanToVisibilityConverter 100%
Daqifi.Desktop.Helpers.EnumDescriptionConverter 100% 91.6%
Daqifi.Desktop.Helpers.IntToVisibilityConverter 100% 100%
Daqifi.Desktop.Helpers.MinMaxDownsampler 100% 96.4%
Daqifi.Desktop.Helpers.NaturalSortHelper 100% 100%
Daqifi.Desktop.Helpers.OxyPlotDarkTheme 100%
Daqifi.Desktop.Helpers.ReferenceComparer`1 100%
Daqifi.Desktop.Helpers.TileBrushes 100%
Daqifi.Desktop.Helpers.UiThreadHelper 26.6% 25%
Daqifi.Desktop.Logger.DatabaseLogger 0% 0%
Daqifi.Desktop.Logger.DatabaseMigrator 0% 0%
Daqifi.Desktop.Logger.DeviceLegendGroup 100% 100%
Daqifi.Desktop.Logger.InitialSessionLoad 100%
Daqifi.Desktop.Logger.LoggedSeriesLegendItem 94.1% 44.4%
Daqifi.Desktop.Logger.LoggingContext 100%
Daqifi.Desktop.Logger.LoggingContextDesignTimeFactory 0%
Daqifi.Desktop.Logger.LoggingManager 25.8% 26.7%
Daqifi.Desktop.Logger.LoggingSession 36.5% 13%
Daqifi.Desktop.Logger.MinimapPlotComponents 100%
Daqifi.Desktop.Logger.PlotLogger 68.8% 63.6%
Daqifi.Desktop.Logger.PlotModelFactory 99.4% 62.5%
Daqifi.Desktop.Logger.SessionChannelInfo 100%
Daqifi.Desktop.Logger.SessionDataRepository 97.9% 89.5%
Daqifi.Desktop.Logger.SessionDeviceMetadata 80%
Daqifi.Desktop.Logger.SessionSampleWriter 96% 91.3%
Daqifi.Desktop.Logger.SummaryLogger 88.2% 71.7%
Daqifi.Desktop.Logger.TimestampGapDetector 94.7% 83.3%
Daqifi.Desktop.Loggers.AppLoggerLoggerProvider 59.5% 56.6%
Daqifi.Desktop.Loggers.ImportOptions 100%
Daqifi.Desktop.Loggers.ImportProgress 0% 0%
Daqifi.Desktop.Loggers.ImportTimestampQuality 100% 100%
Daqifi.Desktop.Loggers.SdCardImportResult 100%
Daqifi.Desktop.Loggers.SdCardSessionImporter 79.4% 70%
Daqifi.Desktop.MainWindow 0% 0%
Daqifi.Desktop.Migrations.AddSamplesSessionTimeIndex 97.8%
Daqifi.Desktop.Migrations.AddSessionDeviceMetadata 98.6%
Daqifi.Desktop.Migrations.AddSessionSampleCount 98.1%
Daqifi.Desktop.Migrations.DropChannelTable 65.5%
Daqifi.Desktop.Migrations.InitialSQLiteMigration 97.4%
Daqifi.Desktop.Migrations.LoggingContextModelSnapshot 0%
Daqifi.Desktop.Models.DaqifiSettings 83.7% 100%
Daqifi.Desktop.Models.DebugDataHistory 6.6% 0%
Daqifi.Desktop.Models.DebugDataModel 0% 0%
Daqifi.Desktop.Models.FirmwareOption 61.5% 37.5%
Daqifi.Desktop.Models.Notifications 80%
Daqifi.Desktop.Models.Profile 100%
Daqifi.Desktop.Models.ProfileChannel 100%
Daqifi.Desktop.Models.ProfileDevice 100%
Daqifi.Desktop.Models.SdCardFile 42.8% 0%
Daqifi.Desktop.Models.SdCardLogFormatInfo 94.7% 87.5%
Daqifi.Desktop.Services.NoOpMessageBoxService 0%
Daqifi.Desktop.Services.WindowsPrincipalAdminChecker 0%
Daqifi.Desktop.Services.WpfMessageBoxService 0%
Daqifi.Desktop.UpdateVersion.VersionNotification 84% 54.1%
Daqifi.Desktop.View.ConnectionDialog 0% 0%
Daqifi.Desktop.View.DebugWindow 0% 0%
Daqifi.Desktop.View.DeviceLogsView 0% 0%
Daqifi.Desktop.View.DuplicateDeviceDialog 0% 0%
Daqifi.Desktop.View.ErrorDialog 0% 0%
Daqifi.Desktop.View.ExportDialog 0% 0%
Daqifi.Desktop.View.FirmwareDialog 0% 0%
Daqifi.Desktop.View.Flyouts.LiveGraphFlyout 0% 0%
Daqifi.Desktop.View.Flyouts.NotificationsFlyout 0% 0%
Daqifi.Desktop.View.Flyouts.SummaryFlyout 0% 0%
Daqifi.Desktop.View.MigrationStatusWindow 0% 0%
Daqifi.Desktop.View.MinimapInteractionController 0% 0%
Daqifi.Desktop.View.ProfilesPane 0% 0%
Daqifi.Desktop.View.Prototype.ChannelsPanePrototype 0% 0%
Daqifi.Desktop.View.Prototype.DevicesPanePrototype 0% 0%
Daqifi.Desktop.View.Prototype.LiveGraphPane 0% 0%
Daqifi.Desktop.View.Prototype.LoggedDataPanePrototype 0% 0%
Daqifi.Desktop.View.SuccessDialog 0% 0%
Daqifi.Desktop.ViewModels.ChannelsPaneViewModel 57.8% 34.8%
Daqifi.Desktop.ViewModels.ChannelTileViewModel 72.6% 66.1%
Daqifi.Desktop.ViewModels.ConfirmOverlayViewModel 100% 100%
Daqifi.Desktop.ViewModels.ConnectionDialogViewModel 66.1% 55.7%
Daqifi.Desktop.ViewModels.DaqifiViewModel 19.4% 14.3%
Daqifi.Desktop.ViewModels.DeviceLogsViewModel 80.4% 60.8%
Daqifi.Desktop.ViewModels.DevicesPaneViewModel 0% 0%
Daqifi.Desktop.ViewModels.DeviceTileViewModel 35.7% 15.2%
Daqifi.Desktop.ViewModels.DuplicateDeviceDialogViewModel 0%
Daqifi.Desktop.ViewModels.ErrorDialogViewModel 0%
Daqifi.Desktop.ViewModels.ExportDialogViewModel 65.5% 50%
Daqifi.Desktop.ViewModels.FirmwareDialogViewModel 55.1% 46.8%
Daqifi.Desktop.ViewModels.ImportAllOutcome 100% 100%
Daqifi.Desktop.ViewModels.LoggingSessionListViewModel 95.6% 88.8%
Daqifi.Desktop.ViewModels.NewProfileChannelItem 0%
Daqifi.Desktop.ViewModels.NewProfileDeviceItem 0% 0%
Daqifi.Desktop.ViewModels.ProfilesPaneViewModel 0% 0%
Daqifi.Desktop.ViewModels.SdCardFailure 100%
Daqifi.Desktop.ViewModels.SdCardFailureClassifier 100% 91.6%
Daqifi.Desktop.ViewModels.SettingsViewModel 0% 0%
Daqifi.Desktop.ViewModels.SuccessDialogViewModel 0%
Sentry.Generated.BuildPropertyInitializer 100%
Daqifi.Desktop.Common - 46.9%
Name Line Branch
Daqifi.Desktop.Common 46.9% 33.3%
Daqifi.Desktop.Common.AppDataPaths 84.2% 50%
Daqifi.Desktop.Common.Loggers.AppLogErrorException 0%
Daqifi.Desktop.Common.Loggers.AppLogger 42% 31.5%

Coverage report generated by ReportGeneratorView full report in build artifacts

@tylerkron

Copy link
Copy Markdown
Contributor Author

Rebased onto main (post-#812), and a bench-status note

Rebased after #812 merged, with the unit gate re-run after the rebase rather than before. All three PRs were already reported textually mergeable, but #808 and #810 modify the same file #812 touched, so "no conflict" was not sufficient reason to skip re-testing.

Hardware gate: partially blocked, and NOT on anything in these PRs

Device-gated FlaUI run on the chain-1 tip: 19/23. Four failures, none attributable to this work:

  • 2 SD imports (SdCardImport_ExistingFileOnCard_ImportsToSession, SdCardLogging_LogsToSdCard_ThenImportsToSession) — both report -1 samples on files dated 2026-06-23 and 2026-07-06. Known firmware SD-heap defect (firmware#703); the bench card has been wedged since June and writes no new files.
  • 2 connect/discovery timeouts (ConfigureLogging_SetsFrequencyAndChannels, RestartStreamingAfterGap_TimeAxisAnchorsOnNewSession) — No device discovered on Serial within 60s and Expected at least 1 connected device tile(s) but found fewer within 60s.

The last two are device state, not code. Established by control, not assumption:

Run Code Result
Earlier today #812 branch RestartStreamingAfterGap PASSED (twice)
Now main, which contains #812 FAILS — same code
Now chain-1 tip FAILS identically

Since the identical code passed earlier and fails now, the variable is the bench, not the branch. A Core-level probe on the same port confirms the device is still alive at the transport layer — connects, initializes, firmware 3.7.2, reports analog_in_port_enabled = FFFF, channel enables survive — so it responds to a direct port open while the app's discovery probe times out. That is consistent with the SD subsystem being wedged by the SD tests that ran immediately before, which per firmware#703 recovers only on a true USB re-enumeration.

This needs a physical power-cycle of the bench device, which I can't perform. I deliberately did not force re-enumeration via pnputil/Device Manager — this hardware has known recovery problems and that is not a risk to take unprompted.

Once the device is power-cycled the device-gated suite should be re-run against these branches to close out the gate. Everything else — build, unit gate, Qodo — is green.

@tylerkron

Copy link
Copy Markdown
Contributor Author

Hardware gate result

Run with parallelism disabled (-- MSTest.Parallelize.Workers=1), which is required for this suite to be deterministic at all — see #814.

Branch Device-gated result
chain-1 (#808 + #810) 21/23 — only the 2 known firmware SD failures
chain-2 (#809) 20/23 — the 2 SD failures + RestartStreamingAfterGap

The extra failure on #809 is not #809

RestartStreamingAfterGap_TimeAxisAnchorsOnNewSession failed with Expected at least 1 connected device tile(s) but found fewer within 60s. Checked rather than assumed:

Identical code passing earlier and failing now, plus main failing the same way, rules out #809. The variable is the bench: discovery degrades as the SD tests accumulate, which is the same behaviour a power cycle cleared earlier today (firmware#703 — recovers only on true USB re-enumeration).

Status

Everything else on all three: build clean, unit gates green, Qodo clean.

@tylerkron

Copy link
Copy Markdown
Contributor Author

Hardware gate: cleared

Re-run on a freshly power-cycled device with -- MSTest.Parallelize.Workers=1 (required — see #814).

The one outstanding question was RestartStreamingAfterGap_TimeAxisAnchorsOnNewSession, which failed in #809's full-suite runs but not #810's. Settled with a controlled back-to-back triplet — same test, run alone, same device state, one after another:

Branch Isolated result
main PASS
#810 tip PASS
#809 PASS

All three pass. The full-suite failure is a harness defect, not a branch one: a preceding test leaves its app running (Application failed to exit in the captured output), that app holds the COM port, and the next test is the one that reports the timeout. The same marker appears in runs on main. Now recorded on #814.

I was wrong twice on the way to this, and both are worth recording so the next person does not repeat them: I first attributed these failures to an SD-induced device wedge (excluding the SD tests made it worse), and then to device degradation across the session (a fresh device reproduced it identically). Neither held up.

Final state, all three PRs

#808 #809 #810
Build clean clean clean
Unit 876 896 872
Qodo Bugs 0, Rules 0 Bugs 0, Rules 0 Bugs 0, Rules 0
Device-gated 21/23 20/23 21/23

Every remaining device-gated failure is accounted for: the two SD imports are the known firmware SD-heap defect (firmware#703) on files dated June/July, and #809's third is the leaked-app harness issue above, disproved by isolated re-run.

Base automatically changed from chore/retire-core-398-bandaids to main August 4, 2026 02:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants