Skip to content

feat: Live Graph redesign + streaming regression fix#495

Merged
tylerkron merged 13 commits intomainfrom
claude/sweet-gates-10c324
Apr 25, 2026
Merged

feat: Live Graph redesign + streaming regression fix#495
tylerkron merged 13 commits intomainfrom
claude/sweet-gates-10c324

Conversation

@tylerkron
Copy link
Copy Markdown
Contributor

@tylerkron tylerkron commented Apr 19, 2026

Summary

This branch grew to cover two related-but-separable changes that both affect the streaming experience: a Live Graph pane redesign that matches the rest of the app's dark theme, and a streaming regression fix where no data appeared on the graph despite the device LED indicating active streaming.

Streaming regression fix

USB-Serial streaming would silently produce no MessageReceived events on Windows. Device LED blinked, byte buffer in the Core consumer grew unbounded (66 → 8202 bytes in 2s observed), Live Graph stayed empty.

Root cause was in daqifi-core ProtobufMessageParser: boot-time garbage on the serial port (USB CDC handshake, DTR pulse artifacts) occasionally varint-encoded a length value in the tens of KB. Under the old 1 MB MaxMessageSizeBytes cap the parser accepted that declared length, took the "wait for more data" branch, and stayed there indefinitely — buffer kept growing, no frames ever parsed.

This PR pulls in that fix via two dependency bumps:

  • chore(deps): bump Daqifi.Core to 0.19.6 — first parser-resync fix (recovery from unparseable bytes via 1-byte advance).
  • chore(deps): bump Daqifi.Core to 0.19.7 — oversized-prefix fix: 4 KB cap on declared frame size, gap gate, and a protobuf field-tag plausibility check on the first body byte. Real partial frames spanning reads are preserved; bogus prefixes are rejected and resync continues.

Also addresses a downstream UNIQUE-constraint failure that surfaced when re-toggling streaming after a session-with-no-samples:

  • fix(logging): UNIQUE constraint when restarting a stream session — Sessions.ID was computed as max(Sessions) + 1, which could collide with orphan rows in SessionDeviceMetadata/Samples left behind by prior crashes or pre-foreign-keys deletes. Now picks max across all three related tables and sweeps orphan metadata at startup.

Live Graph pane redesign

Extracts the Live Graph tab into a dedicated LiveGraphPane UserControl in View/Prototype/, matching the established pattern used by Channels, Devices, and Logged Data panes. Applies the dark visual system throughout — Surface background, design token colors, same typography and spacing language as the exemplar Channels pane.

LiveGraphPane.xaml (new)

  • Surface background fills the entire pane — no more white bleed from the MahApps Light.Blue theme
  • Header bar: green LIVE pulse + MODE / DURATION / RATE chip strip (collapsed when idle), logging toggle label rendered with TextTertiary/StatusGreen DataTrigger instead of the default MahApps pill
  • Plot area: OxyPlot fills the column; plot toolbar floats top-right as a SurfaceRaised-bordered pill containing PlotIconButton icon buttons (zoom-X, zoom-Y, reset, save, settings) separated by BorderDim dividers
  • Channel legend sidebar: SurfaceRaised background, left BorderDim border, CHANNELS section header, compact rows with a 3px color stripe (vs. the previous 16×16 square), TextPrimary channel name, TextTertiary device serial

LiveGraphFlyout.xaml (updated)

  • Flyout Background="#171A20", dark grid lines section with DrawerSectionLabel/DrawerRowLabel styles, precision section with Consolas value readout

MainWindow.xaml (updated)

  • Live Graph TabItem body replaced with <prototype:LiveGraphPane/> via ContentPresenter, cutting ~250 lines from MainWindow

Test plan

Streaming regression

  • Confirmed end-to-end against physical Nyquist1 on Windows: 32 channels (16 analog + 16 digital), 1 Hz, frames flow into Live Graph (HandleChannelUpdate and PlotLogger.Log counts climb in lockstep with arriving frames)
  • Live repro on Mac at 1 Hz and 100 Hz with the same Core build via daqifi-core-example-app --mimic-desktop — no regression on the happy path
  • All 823 daqifi-core tests pass (10 parser tests, including new RecoversFromOversizedPrefix and PreservesPartialFrameAfterGarbage regressions)

Live Graph redesign

  • Build clean — 0 errors
  • Live Graph tab renders with dark background (no white areas)
  • Plot fills the content area; channel sidebar is SurfaceRaised with left border
  • No active channels: sidebar shows "CHANNELS" header with empty list
  • Start logging: header strip animates in with green LIVE dot, MODE, DURATION, and RATE chips; toggle label turns green
  • Stop logging: header strip collapses; toggle label returns to TextTertiary
  • Log-to-Device mode: STORAGE chip appears in the header strip
  • Zoom-X / Zoom-Y / Reset / Save / Settings all fire from the toolbar pill
  • Plot Settings flyout opens from the ⚙ icon
  • Channel swatch click toggles visibility
  • Other tabs (Logged Data, Channels, Devices, Profiles) still render and function

🤖 Generated with Claude Code

Extracts the Live Graph tab into a dedicated LiveGraphPane UserControl
(matching the Channels/Devices/LoggedData pane pattern) and applies the
dark visual system throughout.

- Dark Surface background replaces the default light MahApps theme
- Session status strip shows MODE / DURATION / RATE chips inline with a
  green LIVE pulse indicator when logging; hidden when idle
- Logging toggle label now uses TextTertiary/StatusGreen instead of the
  default MahApps white pill
- Channel legend sidebar uses SurfaceRaised + left BorderDim divider, a
  CHANNELS header, and a narrow color stripe (matching channel tile
  language) instead of a plain rectangle swatch
- Plot toolbar replaced FadeButton (white background, scale-on-hover)
  with PlotIconButton inside a SurfaceRaised bordered group at the
  top-right of the plot; zoom-X, zoom-Y, reset, save, and settings are
  all in one row with dim separators
- LiveGraphFlyout.xaml restyled with a dark #171A20 background, section
  labels, and row-label/toggle layout matching the Devices drawer pattern
- Removes the debug (OpenLogSummaryCommand) button from the toolbar; the
  debug button in the title bar serves that purpose

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@tylerkron tylerkron requested a review from a team as a code owner April 19, 2026 04:20
@qodo-code-review
Copy link
Copy Markdown
Contributor

Review Summary by Qodo

Redesign Live Graph pane with dark theme and unified component pattern

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Extracts Live Graph tab into dedicated LiveGraphPane UserControl matching established pane
  pattern
• Applies dark visual system with Surface background, design tokens, and consistent typography
• Redesigns session status header with inline MODE/DURATION/RATE chips and green LIVE indicator
• Replaces scattered toolbar buttons with unified SurfaceRaised pill containing icon buttons with
  dim dividers
• Refactors channel legend sidebar with SurfaceRaised background, left border, and narrow color
  stripes
• Restyled LiveGraphFlyout with dark background, section labels, and grid-line/precision controls
Diagram
flowchart LR
  A["MainWindow TabItem"] -->|"ContentPresenter"| B["LiveGraphPane UserControl"]
  B --> C["Header Bar"]
  B --> D["Plot Area"]
  B --> E["Channel Sidebar"]
  C --> C1["Session Status Chips<br/>MODE/DURATION/RATE"]
  C --> C2["Logging Toggle<br/>with Label"]
  D --> D1["OxyPlot"]
  D --> D2["Plot Toolbar Pill<br/>Zoom/Reset/Save/Settings"]
  E --> E1["CHANNELS Header"]
  E --> E2["Channel List<br/>with Color Stripes"]
  F["LiveGraphFlyout"] --> F1["Grid Lines Section"]
  F --> F2["Precision Slider<br/>with Consolas Display"]
Loading

Grey Divider

File Changes

1. Daqifi.Desktop/View/Prototype/LiveGraphPane.xaml.cs ✨ Enhancement +12/-0

LiveGraphPane code-behind initialization

• New code-behind file for LiveGraphPane UserControl
• Minimal implementation with InitializeComponent() call
• Follows standard WPF UserControl pattern

Daqifi.Desktop/View/Prototype/LiveGraphPane.xaml.cs


2. Daqifi.Desktop/View/Prototype/LiveGraphPane.xaml ✨ Enhancement +419/-0

New LiveGraphPane UserControl with dark theme design

• New 419-line UserControl replacing inline Live Graph tab content from MainWindow
• Header bar with session status chips (MODE/DURATION/RATE) and logging toggle label using
 TextTertiary/StatusGreen DataTrigger
• Plot area with OxyPlot filling full column and floating SurfaceRaised toolbar pill containing
 icon buttons (zoom-X, zoom-Y, reset, save, settings) separated by BorderDim dividers
• Channel legend sidebar with SurfaceRaised background, left BorderDim border, "CHANNELS"
 header, and compact rows featuring 3px color stripes instead of 16×16 squares
• Custom styles for PlotIconButton and LegendItemButton with hover effects and design token
 colors

Daqifi.Desktop/View/Prototype/LiveGraphPane.xaml


3. Daqifi.Desktop/MainWindow.xaml ✨ Enhancement +7/-250

Replace inline Live Graph content with LiveGraphPane

• Replaces ~250 lines of inline Live Graph tab content with ContentPresenter binding to
 LiveGraphPane
• Removes old Grid layout, session status panel, scattered toolbar buttons, and channel ListView
• Simplifies MainWindow by extracting Live Graph UI into dedicated UserControl

Daqifi.Desktop/MainWindow.xaml


View more (1)
4. Daqifi.Desktop/View/Flyouts/LiveGraphFlyout.xaml ✨ Enhancement +124/-40

Restyle LiveGraphFlyout with dark drawer pattern

• Restyled with dark background #171A20 and foreground #F5F5F7
• Reorganized into two sections: "GRID LINES" and "PRECISION" with DrawerSectionLabel style
• Grid toggles (Major/Minor X/Y-Axis) now use DrawerRowLabel style and FlyoutToggle style for
 consistent drawer aesthetic
• Precision slider repositioned horizontally with Consolas font value display on the right
• Removed old two-column GroupBox layout in favor of cleaner border-separated sections
• Added iconPacks namespace for potential icon support

Daqifi.Desktop/View/Flyouts/LiveGraphFlyout.xaml


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown
Contributor

qodo-code-review Bot commented Apr 19, 2026

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (1) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Hardcoded hex colors in flyout📘 Rule violation ✧ Quality
Description
LiveGraphFlyout.xaml hardcodes multiple color hex values (e.g., #171A20, #F5F5F7) instead of
referencing the existing design token brushes, diverging from the documented design-system usage.
This makes the UI harder to maintain and can cause drift if token values change.
Code

Daqifi.Desktop/View/Flyouts/LiveGraphFlyout.xaml[R15-57]

+             Header="Plot Settings"
+             Background="#171A20"
+             Foreground="#F5F5F7">
+
+    <controls:Flyout.Resources>
+
+        <!-- Brushes come from /Resources/DesignTokens.xaml via App.xaml -->
+
+        <Style x:Key="DrawerSectionLabel" TargetType="TextBlock">
+            <Setter Property="FontSize" Value="10"/>
+            <Setter Property="FontWeight" Value="SemiBold"/>
+            <Setter Property="Foreground" Value="#5A5E66"/>
+            <Setter Property="Margin" Value="0,0,0,10"/>
+        </Style>
+
+        <Style x:Key="DrawerRowLabel" TargetType="TextBlock">
+            <Setter Property="FontSize" Value="11"/>
+            <Setter Property="Foreground" Value="#8E9199"/>
+            <Setter Property="VerticalAlignment" Value="Center"/>
+        </Style>
+
+        <!-- Dark toggle switch inside the flyout -->
+        <Style x:Key="FlyoutToggle" TargetType="controls:ToggleSwitch"
+               BasedOn="{StaticResource MahApps.Styles.ToggleSwitch}">
+            <Setter Property="Foreground" Value="#F5F5F7"/>
+            <Setter Property="OffContent" Value="Off"/>
+            <Setter Property="OnContent" Value="On"/>
+        </Style>
+
+    </controls:Flyout.Resources>
+
+    <Grid Background="#171A20" Margin="0,8,0,0">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
-        <GroupBox Grid.Column="1" Grid.Row="0" Header="Grid Settings">
-            <Grid>
-                <Grid.RowDefinitions>
-                    <RowDefinition Height="Auto"/>
-                    <RowDefinition Height="Auto"/>
-                    <RowDefinition Height="Auto"/>
-                    <RowDefinition Height="Auto"/>
-                </Grid.RowDefinitions>
-                <Grid.ColumnDefinitions>
-                    <ColumnDefinition Width="Auto"/>
-                    <ColumnDefinition Width="Auto"/>
-                </Grid.ColumnDefinitions>
-                <Label Grid.Row="0" Grid.Column="0" Content="Major X-Axis" VerticalAlignment="Center"/>
-                <controls:ToggleSwitch Grid.Row="0" Grid.Column="1" IsOn="{Binding Plotter.ShowingMajorXAxisGrid, Mode=TwoWay}" OffContent="Off" OnContent="On" HorizontalAlignment="Left"/>
-                <Label Grid.Row="1" Grid.Column="0" Content="Minor X-Axis" VerticalAlignment="Center"/>
-                <controls:ToggleSwitch Grid.Row="1" Grid.Column="1" IsOn="{Binding Plotter.ShowingMinorXAxisGrid, Mode=TwoWay}" OffContent="Off" OnContent="On" HorizontalAlignment="Left"/>
-                <Label Grid.Row="2" Grid.Column="0" Content="Major Y-Axis" VerticalAlignment="Center"/>
-                <controls:ToggleSwitch Grid.Row="2" Grid.Column="1" IsOn="{Binding Plotter.ShowingMajorYAxisGrid, Mode=TwoWay}" OffContent="Off" OnContent="On" HorizontalAlignment="Left"/>
-                <Label Grid.Row="3" Grid.Column="0" Content="Minor Y-Axis" VerticalAlignment="Center"/>
-                <controls:ToggleSwitch Grid.Row="3" Grid.Column="1" IsOn="{Binding Plotter.ShowingMinorYAxisGrid, Mode=TwoWay}" OffContent="Off" OnContent="On" HorizontalAlignment="Left"/>
-            </Grid>
-        </GroupBox>
-        <GroupBox Grid.Column="0" Grid.Row="0" Header="Graph Precision">
-            <Grid>
-                <Grid.RowDefinitions>
-                    <RowDefinition Height="Auto"/>
-                    <RowDefinition Height="Auto"/>
-                </Grid.RowDefinitions>
-                <Label Grid.Row="0" Content="{Binding Plotter.Precision, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Center" VerticalAlignment="Center" HorizontalContentAlignment="Center" FontSize="20"/>
-                <Slider Grid.Row="1" HorizontalAlignment="Stretch" Minimum="0" Maximum="10" TickFrequency="1" IsSnapToTickEnabled="True" Value="{Binding Plotter.Precision, UpdateSourceTrigger=PropertyChanged}"/>
-            </Grid>
-        </GroupBox>
+
+        <!-- ── Grid visibility ── -->
+        <Border Grid.Row="0"
+                BorderBrush="#2A2F38"
+                BorderThickness="0,0,0,1"
+                Padding="20,16,20,16">
Evidence
PR Compliance ID 368570 requires new/redesigned UI to align with the design philosophy and Channels
pane exemplar, including using shared design tokens where applicable. The flyout uses literal hex
colors for background/foreground/borders even though equivalent brushes exist in
Resources/DesignTokens.xaml (e.g., SurfaceRaised, TextPrimary, TextSecondary,
TextTertiary, BorderDim).

Rule 368570: Align new UI surfaces with documented design philosophy and Channels pane exemplar
Daqifi.Desktop/View/Flyouts/LiveGraphFlyout.xaml[15-57]
Daqifi.Desktop/Resources/DesignTokens.xaml[11-25]

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

## Issue description
`LiveGraphFlyout.xaml` hardcodes colors that match existing design token brushes (e.g., `#171A20`, `#F5F5F7`, `#2A2F38`, `#8E9199`, `#5A5E66`) instead of using `{StaticResource SurfaceRaised}`, `{StaticResource TextPrimary}`, `{StaticResource BorderDim}`, etc.

## Issue Context
The design system already defines these colors in `Resources/DesignTokens.xaml`, and the Channels pane exemplar largely references those tokens. Hardcoding token values creates maintenance drift when tokens evolve.

## Fix Focus Areas
- Daqifi.Desktop/View/Flyouts/LiveGraphFlyout.xaml[15-57]
- Daqifi.Desktop/Resources/DesignTokens.xaml[11-25]

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


2. LiveGraphPane() constructor missing XML docs📘 Rule violation ✧ Quality
Description
The new public constructor LiveGraphPane() lacks an XML documentation comment with a <summary>,
which violates the requirement that all public API members be documented. This can break
documentation generation and reduces API discoverability in IDE tooling.
Code

Daqifi.Desktop/View/Prototype/LiveGraphPane.xaml.cs[R8-11]

+    public LiveGraphPane()
+    {
+        InitializeComponent();
+    }
Evidence
PR Compliance ID 244813 requires XML documentation comments for all new/modified public members.
LiveGraphPane is declared public, but its public constructor has no preceding /// XML doc
comment.

Rule 244813: Require XML documentation comments for all public API members
Daqifi.Desktop/View/Prototype/LiveGraphPane.xaml.cs[6-11]

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

## Issue description
`LiveGraphPane()` is a public constructor without an XML documentation comment (`/// <summary>...`).

## Issue Context
The project compliance rules require XML documentation comments for all public API members, including constructors.

## Fix Focus Areas
- Daqifi.Desktop/View/Prototype/LiveGraphPane.xaml.cs[6-11]

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



Remediation recommended

3. UI blocked by MAX queries🐞 Bug ➹ Performance
Description
Starting logging now runs three synchronous MAX queries (including on Samples) inside
OnActiveChanged, which is triggered directly by the UI-bound IsLogging toggle; this can noticeably
freeze the UI on large databases. The added work happens every logging start and is on the critical
interaction path.
Code

Daqifi.Desktop/Loggers/LoggingManager.cs[R115-119]

+                var sessionsMax = context.Sessions.AsNoTracking().Select(s => (int?)s.ID).Max();
+                var metadataMax = context.SessionDeviceMetadata.AsNoTracking().Select(m => (int?)m.LoggingSessionID).Max();
+                var samplesMax = context.Samples.AsNoTracking().Select(s => (int?)s.LoggingSessionID).Max();
+                var maxKnownId = Math.Max(sessionsMax ?? -1, Math.Max(metadataMax ?? -1, samplesMax ?? -1));
+                var newId = maxKnownId + 1;
Evidence
The new session ID logic executes three MAX queries (Sessions, SessionDeviceMetadata, Samples)
before creating the session. The IsLogging setter calls LoggingManager.Instance.Active synchronously
(typical UI thread path), so these DB operations add blocking latency to the toggle. Samples is the
high-cardinality table (per-sample rows), making MAX potentially expensive as the DB grows.

Daqifi.Desktop/Loggers/LoggingManager.cs[106-123]
Daqifi.Desktop/ViewModels/DaqifiViewModel.cs[168-205]
Daqifi.Desktop/Loggers/LoggingContext.cs[35-40]

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

### Issue description
`LoggingManager.OnActiveChanged` synchronously queries `MAX()` across multiple tables (including `Samples`) to compute the next session ID. Because `IsLogging` is toggled via a UI-bound property, this work can block the UI thread and cause noticeable freezes on large logging databases.

### Issue Context
The MAX-based ID selection is addressing real orphan-row/collision scenarios, so the goal is to preserve correctness while removing synchronous DB work from the UI interaction path.

### Fix Focus Areas
- Daqifi.Desktop/Loggers/LoggingManager.cs[106-119]

### Suggested fix approach
- Move session creation (including DB MAX queries) off the UI thread (e.g., trigger it from an async command / background task and marshal results back to the dispatcher), **or**
- Cache and maintain a `nextSessionId` value in memory initialized once at startup (single DB query) and increment atomically per session creation, **or**
- Reduce round-trips by computing `maxKnownId` in a single SQL statement (UNION + MAX) if you keep it synchronous, then consider background execution for the remaining cost.

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


4. ExecuteSqlRaw blocks startup thread 📘 Rule violation ➹ Performance
Description
LoadPersistedLoggingSessions() performs a synchronous ExecuteSqlRaw call, which can block the UI
thread if this method is invoked during UI initialization or from UI-bound commands. This can cause
visible hangs/freezes during app startup or when reloading sessions.
Code

Daqifi.Desktop/Loggers/LoggingManager.cs[R601-603]

+            context.Database.ExecuteSqlRaw(
+                "DELETE FROM SessionDeviceMetadata WHERE LoggingSessionID NOT IN (SELECT ID FROM Sessions)");
+        }
Evidence
PR Compliance ID 244825 disallows blocking synchronous calls on the UI thread. The new cleanup logic
issues a synchronous SQL command via context.Database.ExecuteSqlRaw(...), which is a blocking DB
operation and risks running on the UI thread in typical MVVM initialization flows.

Rule 244825: Disallow blocking synchronous calls on the UI thread
Daqifi.Desktop/Loggers/LoggingManager.cs[592-607]

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

## Issue description
`LoadPersistedLoggingSessions()` runs `context.Database.ExecuteSqlRaw(...)` synchronously for orphan cleanup, which can block the UI thread.

## Issue Context
This method is part of session-loading logic and may be called during UI initialization or other UI-thread flows. The DB cleanup should not run as a blocking call on the UI thread.

## Fix Focus Areas
- Daqifi.Desktop/Loggers/LoggingManager.cs[592-607]

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



Advisory comments

5. Hover overrides checked border🐞 Bug ⚙ Maintainability
Description
The LoggingToggle template sets Track.BorderBrush in both the IsChecked and IsMouseOver triggers, so
hovering a checked (logging-on) toggle replaces the green border with TextSecondary. This slightly
undermines the visual clarity of the “on” state while interacting with the toggle.
Code

Daqifi.Desktop/View/Prototype/LiveGraphPane.xaml[R74-83]

+                            <Trigger Property="IsChecked" Value="True">
+                                <Setter TargetName="Track" Property="Background" Value="{StaticResource StatusGreen}"/>
+                                <Setter TargetName="Track" Property="BorderBrush" Value="{StaticResource StatusGreen}"/>
+                                <Setter TargetName="Thumb" Property="Fill" Value="White"/>
+                                <Setter TargetName="Thumb" Property="HorizontalAlignment" Value="Right"/>
+                                <Setter TargetName="Thumb" Property="Margin" Value="0,0,2,0"/>
+                            </Trigger>
+                            <Trigger Property="IsMouseOver" Value="True">
+                                <Setter TargetName="Track" Property="BorderBrush" Value="{StaticResource TextSecondary}"/>
+                            </Trigger>
Evidence
In the control template triggers, both IsChecked=True and IsMouseOver=True set the same property
(Track.BorderBrush). When both conditions are true, the mouseover setter will override the
checked-state border brush, changing it from StatusGreen to TextSecondary while hovered.

Daqifi.Desktop/View/Prototype/LiveGraphPane.xaml[73-87]

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

### Issue description
The `LoggingToggle` template uses separate triggers for `IsChecked` and `IsMouseOver` that both set `Track.BorderBrush`. When the toggle is checked and hovered, the hover trigger overwrites the green border.

### Issue Context
This is minor visual correctness/polish, but easy to fix by making the hovered+checked case explicit.

### Fix Focus Areas
- Daqifi.Desktop/View/Prototype/LiveGraphPane.xaml[73-87]

### Suggested fix approach
- Replace the `IsMouseOver` trigger with a `MultiTrigger` (or reorder/add conditions) so that:
 - Checked+Hovered keeps `StatusGreen` (or uses a slightly different green), and
 - Unchecked+Hovered uses `TextSecondary` as today.

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


Grey Divider

Qodo Logo

Comment thread Daqifi.Desktop/View/Prototype/LiveGraphPane.xaml.cs
Comment thread Daqifi.Desktop/View/Flyouts/LiveGraphFlyout.xaml
tylerkron and others added 8 commits April 18, 2026 22:28
Cleanup pass after design-philosophy review:
- Drop dead SessionDuration/StorageUsage chip bindings (properties don't exist)
- Move plot toolbar pill into the header bar so it no longer overlaps the
  right Y-axis labels
- Restore the log-summary debug button (Bug icon) inside the toolbar pill
- Replace hardcoded hex in LiveGraphFlyout with DesignTokens DynamicResource
  references so the flyout follows the dark theme tokens

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Addresses Qodo rule 244813 (XML docs required on public API members).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Live Graph empty states (no channels streaming):
- Centered overlay on the plot area with chart icon and CTA pointing
  users to the Channels tab
- Inline hint in the Channels sidebar so the panel isn't a blank column

Window chrome:
- Override MahApps Light.Blue title bar with Surface/TextPrimary tokens
  so the title bar matches the dark panes instead of glowing blue
- Swap the hardcoded "Red" notification badge to the StatusRed token

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When the user has connected a device and enabled channels but hasn't
flipped the LOGGING toggle yet, the plot was just an empty grid with
no signal that toggling logging is what makes data appear. Adds a
second overlay (green play icon, "Channels ready · Toggle LOGGING ON
to start streaming") triggered when IsLogging is false and at least
one input channel is active.

Backed by a new HasActiveInputChannels derived property on
DaqifiViewModel since MultiDataTrigger can only do equality checks
and a "Count > 0" condition needs a bool. Mirrors the existing
HasLoggingSessions pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The default MahApps ToggleSwitch off-state had near-zero contrast against
the dark plot card, making it hard for users to see whether logging was
actually toggled. Swap in a custom ToggleButton template: a dim track with
a gray thumb when off, StatusGreen with a white thumb when on. The on
state now matches the LIVE pulse and "LOGGING ON" label color, so the
active state reads at a glance.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Toggling logging on, off (with no samples), then on again could throw
'UNIQUE constraint failed: SessionDeviceMetadata.LoggingSessionID,
SessionDeviceMetadata.DeviceSerialNo'. The new session ID was computed as
max(Sessions.ID) + 1, but if SessionDeviceMetadata or Samples held orphan
rows from a prior crash (or a delete that ran without SQLite foreign keys
enabled), the chosen ID could already be referenced — and the composite
PK on SessionDeviceMetadata rejected the insert.

Compute the next ID as max(Sessions, SessionDeviceMetadata, Samples) + 1
so it can't collide with any leftover reference, and sweep orphan
metadata at startup so the database self-heals on the next launch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Picks up the protobuf parser fix from daqifi-core#169 — the parser now
resyncs past leading garbage instead of stalling after 3 retries, so
the desktop Live Graph receives stream messages even when boot-time
junk lands at the head of the consumer buffer on serial connect.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The "Channels ready / Toggle LOGGING ON" overlay covered the plot
whenever logging was off and channels were active — including right
after a session ended, hiding the data the user just captured. The
LOGGING toggle is already prominent in the toolbar, so the prompt is
unnecessary. The "No channels streaming" overlay (shown when no
channels are active) is preserved.

Also removes the now-unused HasActiveInputChannels property and its
PropertyChanged firing, which only existed for this overlay's binding.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@tylerkron tylerkron force-pushed the claude/sweet-gates-10c324 branch from a92062e to e355560 Compare April 20, 2026 04:05
tylerkron and others added 3 commits April 23, 2026 21:03
Diagnostic-only. Adds counter-based logs at every junction in the
stream data path so one run can localize whether messages are
arriving from Core, where they get gated, and whether samples reach
the plot. Also logs BuildDeviceMetadataForSession output so the EF
duplicate-key error on session restart can be attributed to a
specific serial.

Touchpoints:
  - Serial/WiFi OnCoreMessageReceived: raw count from Core
  - OnStreamMessageReceived: received / gated / processed counts
  - InitializeStreaming: end-of-start snapshot of IsStreaming, Mode,
    active analog/digital channel counts
  - LoggingManager.OnActiveChanged(true): subscribed channels grouped
    by serial, connected-device serials
  - BuildDeviceMetadataForSession: returned rows + in-loop duplicate
    detection to pinpoint the EF tracker conflict
  - HandleDeviceMessage / HandleChannelUpdate: counts + gate state
  - PlotLogger.Log: samples reaching the plot

All logs are Interlocked-counted and emit at count 1, 10, and every
100 after — cheap in the hot path, informative at session boundaries.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pulls in the oversized-prefix parser stall fix (daqifi-core#173) on top
of the v0.19.6 leading-garbage recovery. Addresses the streaming
regression where boot-time serial garbage that happens to varint-encode
a large length left the parser waiting indefinitely — device LED
blinking, buffer growing, zero MessageReceived events fired.

[STREAM_DIAG] instrumentation from b507a97 stays in place until this
lands on a physical device and we confirm frames flow end-to-end.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@tylerkron tylerkron changed the title feat: redesign Live Graph pane following design philosophy feat: Live Graph redesign + streaming regression fix Apr 25, 2026
@tylerkron
Copy link
Copy Markdown
Contributor Author

/agentic_review

@qodo-code-review
Copy link
Copy Markdown
Contributor

Code Review by Qodo

Grey Divider

Looking for bugs?

Check back in a few minutes. An AI review agent is analyzing this pull request.

Grey Divider

Qodo Logo

@tylerkron
Copy link
Copy Markdown
Contributor Author

/agentic_review

@qodo-code-review
Copy link
Copy Markdown
Contributor

qodo-code-review Bot commented Apr 25, 2026

Persistent review updated to latest commit 3cb00c1

…e border on hover

Two findings from Qodo review on PR #495:

1. UI blocked by MAX queries (Bug, Performance). The session-ID
   collision-safe lookup ran three round-trips on the UI thread (one
   per related table) every time logging started. Folded into a single
   SQL using UNION ALL so the round-trip count drops to one. Each
   inner MAX hits an index — Sessions.ID is the PK, the
   SessionDeviceMetadata composite PK leads with LoggingSessionID, and
   IX_Samples_SessionTime covers Samples.LoggingSessionID — so SQLite
   resolves each inner MAX as an index seek (verified via
   EXPLAIN QUERY PLAN: "SEARCH Samples USING COVERING INDEX"). Behavior
   is preserved: empty tables yield -1+1=0, orphans in any table push
   the next ID safely past them.

2. Hover overrides checked border (Bug, Maintainability). The
   LoggingToggle template had a bare IsMouseOver=true Trigger setting
   Track.BorderBrush, which overrode the IsChecked=true setter when
   both held — swapping the green "logging on" outline for
   TextSecondary while hovered. Replaced with a MultiTrigger gated on
   IsChecked=False so hover styling only applies in the unchecked
   state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@tylerkron
Copy link
Copy Markdown
Contributor Author

Triaged the latest Qodo review (3 new findings on top of the streaming-fix commits). Resolved in 96e91a6:

4. UI blocked by MAX queries (Bug, Performance — agreed and fixed)
Three synchronous MAX round-trips on the UI thread per logging-start were unnecessary. Collapsed into a single SQL via UNION ALL:

SELECT MAX(id) AS Value FROM (
    SELECT MAX(ID)              AS id FROM Sessions
    UNION ALL SELECT MAX(LoggingSessionID) FROM SessionDeviceMetadata
    UNION ALL SELECT MAX(LoggingSessionID) FROM Samples
)

Each inner MAX is an index seek — Sessions.ID is the PK, SessionDeviceMetadata.LoggingSessionID leads its composite PK, and IX_Samples_SessionTime covers Samples.LoggingSessionID (verified via EXPLAIN QUERY PLAN: SEARCH Samples USING COVERING INDEX IX_Samples_SessionTime). Behavior preserved across the relevant scenarios (empty tables → NULL → -1+1=0; orphans in any table push the next ID safely past).

5. Hover overrides checked border (Bug, Maintainability — agreed and fixed)
Replaced the bare IsMouseOver=true trigger with a MultiTrigger gated on IsChecked=False so the green "logging on" outline survives hover. Comment in the XAML calls out why.

3. ExecuteSqlRaw blocks startup thread (Rule violation, Performance — partial disagree)
Real concern in principle but I don't think this PR is the right place to fix it. LoadPersistedLoggingSessions() is entirely synchronous DB-bound work — it already runs RemoveRange + SaveChanges to drop empty sessions, fetches the session list with eager-loaded metadata, and runs BackfillMissingSampleCounts. The added ExecuteSqlRaw is a single DELETE that affects only orphan rows (typically zero), runs once per app load, and is no more blocking than the surrounding queries. Making this one statement async without making the rest async wouldn't change observed behavior.

The principled fix is making the whole load path async (LoadPersistedLoggingSessionsAsync) and updating call sites — but that's a method-signature change that ripples through several VMs and is outside the scope of a parser-stall regression PR. I'd rather track it as a follow-up than fragment the threading model here. Marking as wontfix-on-this-PR; happy to spawn a separate task if you'd like.

(The two earlier findings — hardcoded hex colors in the flyout and missing XML doc on LiveGraphPane() — were already resolved in the original Live Graph commits before the streaming work landed.)

@github-actions
Copy link
Copy Markdown

📊 Code Coverage Report

Summary

Summary
Generated on: 4/25/2026 - 4:37:40 AM
Coverage date: 4/25/2026 - 4:37:07 AM - 4/25/2026 - 4:37:36 AM
Parser: MultiReport (4x Cobertura)
Assemblies: 3
Classes: 119
Files: 148
Line coverage: 17.8% (1507 of 8455)
Covered lines: 1507
Uncovered lines: 6948
Coverable lines: 8455
Total lines: 25995
Branch coverage: 18.9% (521 of 2752)
Covered branches: 521
Total branches: 2752
Method coverage: Feature is only available for sponsors

Coverage

DAQiFi - 17.6%
Name Line Branch
DAQiFi 17.6% 18.9%
Daqifi.Desktop.App 5.4% 0%
Daqifi.Desktop.Channel.AbstractChannel 40.9% 27.7%
Daqifi.Desktop.Channel.AnalogChannel 58.7% 25%
Daqifi.Desktop.Channel.Channel 11.5% 0%
Daqifi.Desktop.Channel.ChannelColorManager 100% 100%
Daqifi.Desktop.Channel.DataSample 91.6%
Daqifi.Desktop.Channel.DigitalChannel 65.2% 25%
Daqifi.Desktop.Commands.CompositeCommand 0% 0%
Daqifi.Desktop.Commands.HostCommands 0%
Daqifi.Desktop.Commands.WeakEventHandlerManager 0% 0%
Daqifi.Desktop.Configuration.FirewallConfiguration 90.6% 66.6%
Daqifi.Desktop.Configuration.WindowsFirewallWrapper 64% 68.4%
Daqifi.Desktop.ConnectionManager 42.4% 39.2%
Daqifi.Desktop.Converters.BoolToActiveStatusConverter 0% 0%
Daqifi.Desktop.Converters.BoolToConnectionStatusConverter 0% 0%
Daqifi.Desktop.Converters.BoolToStatusColorConverter 0% 0%
Daqifi.Desktop.Converters.BrushColorMatchConverter 0% 0%
Daqifi.Desktop.Converters.ConnectionTypeToColorConverter 0% 0%
Daqifi.Desktop.Converters.ConnectionTypeToUsbConverter 0% 0%
Daqifi.Desktop.Converters.InvertedBoolToVisibilityConverter 0% 0%
Daqifi.Desktop.Converters.ListToStringConverter 0% 0%
Daqifi.Desktop.Converters.NotNullToVisibilityConverter 0% 0%
Daqifi.Desktop.Converters.OxyColorToBrushConverter 0% 0%
Daqifi.Desktop.Device.AbstractStreamingDevice 42.9% 38.6%
Daqifi.Desktop.Device.DeviceMessage 0%
Daqifi.Desktop.Device.Firmware.BootloaderSessionStreamingDeviceAdapter 0% 0%
Daqifi.Desktop.Device.Firmware.WifiPromptDelayProcessRunner 0% 0%
Daqifi.Desktop.Device.NativeMethods 100%
Daqifi.Desktop.Device.SerialDevice.SerialStreamingDevice 27.6% 30.8%
Daqifi.Desktop.Device.WiFiDevice.DaqifiStreamingDevice 40.9% 39.4%
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.DuplicateDeviceCheckResult 100%
Daqifi.Desktop.Exporter.OptimizedLoggingSessionExporter 66.5% 62.7%
Daqifi.Desktop.Exporter.SampleData 100%
Daqifi.Desktop.Helpers.BooleanConverter`1 0% 0%
Daqifi.Desktop.Helpers.BooleanToInverseBoolConverter 0% 0%
Daqifi.Desktop.Helpers.BooleanToVisibilityConverter 0%
Daqifi.Desktop.Helpers.EnumDescriptionConverter 100% 100%
Daqifi.Desktop.Helpers.IntToVisibilityConverter 0% 0%
Daqifi.Desktop.Helpers.MinMaxDownsampler 98.6% 97.9%
Daqifi.Desktop.Helpers.MyMultiValueConverter 0%
Daqifi.Desktop.Helpers.NaturalSortHelper 100% 100%
Daqifi.Desktop.Helpers.OxyPlotDarkTheme 0%
Daqifi.Desktop.Helpers.VersionHelper 98.2% 66.2%
Daqifi.Desktop.Logger.DatabaseLogger 0% 0%
Daqifi.Desktop.Logger.DatabaseMigrator 0% 0%
Daqifi.Desktop.Logger.DeviceLegendGroup 100% 100%
Daqifi.Desktop.Logger.LoggedSeriesLegendItem 0% 0%
Daqifi.Desktop.Logger.LoggingContext 100%
Daqifi.Desktop.Logger.LoggingContextDesignTimeFactory 0%
Daqifi.Desktop.Logger.LoggingManager 0% 0%
Daqifi.Desktop.Logger.LoggingSession 16% 5%
Daqifi.Desktop.Logger.PlotLogger 0% 0%
Daqifi.Desktop.Logger.SessionDeviceMetadata 80%
Daqifi.Desktop.Logger.SummaryLogger 0% 0%
Daqifi.Desktop.Logger.TimestampGapDetector 95% 83.3%
Daqifi.Desktop.Loggers.ImportOptions 0%
Daqifi.Desktop.Loggers.ImportProgress 0% 0%
Daqifi.Desktop.Loggers.SdCardSessionImporter 0% 0%
Daqifi.Desktop.MainWindow 0% 0%
Daqifi.Desktop.Migrations.AddSamplesSessionTimeIndex 0%
Daqifi.Desktop.Migrations.AddSessionDeviceMetadata 0%
Daqifi.Desktop.Migrations.AddSessionSampleCount 0%
Daqifi.Desktop.Migrations.InitialSQLiteMigration 0%
Daqifi.Desktop.Migrations.LoggingContextModelSnapshot 0%
Daqifi.Desktop.Models.AddProfileModel 0%
Daqifi.Desktop.Models.DaqifiSettings 80.5% 83.3%
Daqifi.Desktop.Models.DebugDataCollection 6.6% 0%
Daqifi.Desktop.Models.DebugDataModel 0% 0%
Daqifi.Desktop.Models.Notifications 0%
Daqifi.Desktop.Models.SdCardFile 0% 0%
Daqifi.Desktop.Services.WindowsPrincipalAdminChecker 0%
Daqifi.Desktop.Services.WpfMessageBoxService 0%
Daqifi.Desktop.UpdateVersion.VersionNotification 0% 0%
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.FirmwareFlyout 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 0% 0%
Daqifi.Desktop.ViewModels.ChannelTileViewModel 0% 0%
Daqifi.Desktop.ViewModels.ConnectionDialogViewModel 37.3% 39.1%
Daqifi.Desktop.ViewModels.DaqifiViewModel 17.5% 11%
Daqifi.Desktop.ViewModels.DeviceLogsViewModel 0% 0%
Daqifi.Desktop.ViewModels.DevicesPaneViewModel 0% 0%
Daqifi.Desktop.ViewModels.DeviceTileViewModel 0% 0%
Daqifi.Desktop.ViewModels.DuplicateDeviceDialogViewModel 0%
Daqifi.Desktop.ViewModels.ErrorDialogViewModel 0%
Daqifi.Desktop.ViewModels.ExportDialogViewModel 0% 0%
Daqifi.Desktop.ViewModels.FirmwareDialogViewModel 0% 0%
Daqifi.Desktop.ViewModels.NewProfileChannelItem 0%
Daqifi.Desktop.ViewModels.NewProfileDeviceItem 0% 0%
Daqifi.Desktop.ViewModels.ProfilesPaneViewModel 0% 0%
Daqifi.Desktop.ViewModels.SettingsViewModel 0% 0%
Daqifi.Desktop.ViewModels.SuccessDialogViewModel 85.7%
Daqifi.Desktop.WindowViewModelMapping.IWindowViewModelMappingsContract 0%
Daqifi.Desktop.WindowViewModelMapping.WindowViewModelMappings 0%
Sentry.Generated.BuildPropertyInitializer 100%
Daqifi.Desktop.Common - 30.8%
Name Line Branch
Daqifi.Desktop.Common 30.8% 16.6%
Daqifi.Desktop.Common.Loggers.AppLogger 33.7% 16.6%
Daqifi.Desktop.Common.Loggers.NoOpLogger 0%
Daqifi.Desktop.IO - 100%
Name Line Branch
Daqifi.Desktop.IO 100% ****
Daqifi.Desktop.IO.Messages.MessageEventArgs`1 100%

Coverage report generated by ReportGeneratorView full report in build artifacts

@tylerkron tylerkron merged commit 1be0297 into main Apr 25, 2026
2 checks passed
@tylerkron tylerkron deleted the claude/sweet-gates-10c324 branch April 25, 2026 04:40
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.

1 participant