Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/release-notes/v1.1.6.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
## WinBat Lens v1.1.6

This release adds a battery voltage curve recorded against state of charge, and
fixes a tray icon that hid real sub-watt power flow.

### Highlights

- **New "Battery Voltage History" tab.** The monitor records the battery
driver's measured terminal voltage each time the integer battery percentage
changes, and plots the resulting 0–100% curve.
- Per-percentage average, minimum and maximum voltage, change-event count and
last-recorded time are kept in an exact-value table alongside the chart, with
a clear action.
- The history is persisted per user in LocalAppData, so the curve keeps
building across sessions.
- Percentages that have never been observed are left as gaps in the curve
rather than interpolated — an unmeasured voltage is never drawn as if it had
been read.
- Only voltages the battery driver actually reported are recorded. The internal
nominal voltage used for power math is excluded.
- **Tray icon: sub-watt power is no longer rounded away.** A real charge or
discharge rate between 0.1 W and 0.9 W used to render as `0`, hiding the fact
that power was flowing. Rates below 1 W now show one decimal place; whole-watt
and `99+` display above 1 W is unchanged.

The published executables are unsigned. Windows SmartScreen may show a warning
on first launch.
91 changes: 82 additions & 9 deletions AI_HANDOFF.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,58 @@
# Project State & Handoff

## v1.1.5 release preparation (2026-08-05)
## v1.1.6: battery voltage history by percentage (2026-08-05)

Added a battery voltage history feature. The monitor records a real
battery-driver voltage only when the integer battery percentage changes; the
same percentage is ignored on subsequent one-second polls. The first
observation seeds a percentage that has no prior record, while a percentage
seen again after the battery moves is aggregated as another change event.

`BatteryVoltageHistoryService` persists per-percentage average/minimum/maximum
voltage, event count, and last timestamp in the user's LocalAppData. The new
Battery Voltage History tab plots the observed 0-100% curve, leaves gaps for
unmeasured percentages, and includes an exact-value table plus clear action.
The chart never uses `RealTimePowerState.BatteryVoltageV` when
`IsVoltageMeasured` is false, because that state can carry an internal nominal
voltage used only for power math.

Shipped together with the sub-watt tray icon fix below as **v1.1.6**; release
notes are in `.github/release-notes/v1.1.6.md`.

### Review pass before release

Four defects were found and fixed in the new tab before publication, all in
presentation rather than in the recorded measurements:

1. **The vertical gridlines did not line up with the data.** They were drawn
across the whole container in tenths (`width/10*i`), but the curve is
plotted into an inset area (`plotLeft = 44`, `plotRight = 8`) that leaves
room for the Y-axis labels. `DrawVoltageChartGridlines` now takes
`plotLeft`/`plotWidth` and draws `i = 0..10` across the plot area, so a
gridline sits exactly under the percentage it marks and the plot's own
0%/100% edges are visible.
2. **The 25% and 75% X-axis labels were in the wrong place.** Five equal
columns with the outer two edge-aligned put their centres at 30% and 70%.
The end columns are now `0.5*`, which lands every label on its true
position.
3. **`LastRecordedAt` was bound raw**, so the column rendered a
culture-dependent `DateTime.ToString()` instead of the app's
`yyyy-MM-dd HH:mm:ss` convention. A `LastRecordedAtText` property was added
(and change-notified from `AddSample`) to match `PowerHistoryRecord`.
4. **The XAML placeholder text and the samples-column header disagreed with
the localization strings** they are overwritten with at runtime — the header
said 樣本數 where the string is 變化次數, which is the more accurate name for
what is counted.

Validation after the fixes: Debug and Release solution builds passed with
`-warnaserror` at 0 warnings / 0 errors, `dotnet test` passed 17/17, and
`git diff --check` passed.

## v1.1.5 release and publication (2026-08-05)

The post-v1.1.4 background-startup and comprehensive audit changes are being
prepared for publication as v1.1.5. The project version is now 1.1.5 and the
release notes are in `.github/release-notes/v1.1.5.md`.
published as v1.1.5. The project version is 1.1.5 and the release notes are in
`.github/release-notes/v1.1.5.md`.

The intended release scope is the complete existing working-tree change set:
background startup, tray warmup/idle behavior, removal of unsupported or dead
Expand All @@ -23,8 +71,31 @@ three assets; they are unsigned by design in this environment:
- `WinBatLens_v1.1.5_Setup_x64.exe`: SHA-256
`B4F5C013BA3F6CEAF1D1E917C068304A2CD993E3CB8FE90A2A830DAFA4DDA4B9`

The ZIP contains `WinBatLens.exe`, `README.md`, and `LICENSE`. Publication to
GitHub is the remaining step.
The ZIP contains `WinBatLens.exe`, `README.md`, and `LICENSE`. PR #8 was merged
to `main` at `c9fdbed`, tag `v1.1.5` triggered Release workflow run
`30962533960`, and the normal GitHub Release is published at
`https://github.com/nojackno2-ctrl/WinBat-Lens/releases/tag/v1.1.5`.

The GitHub runner rebuilt the release assets, so the authoritative downloaded
asset hashes are:

- `WinBatLens_v1.1.5_Portable_x64.exe`: SHA-256
`1c8e0757439a0bde196149022bf048f6f7faf91574ff5496d640a07abaf798c9`
- `WinBatLens_v1.1.5_Portable_x64.zip`: SHA-256
`1cdd0edf0908228021ef1ae34e0beec903c670fab2dc4b72ca889d16d6b35d2a`
- `WinBatLens_v1.1.5_Setup_x64.exe`: SHA-256
`c66ca8fe3e8928240917a29d4df8a4364c2b1a3fe571fe443244fd973164cd76`

## Sub-watt tray icon power display (2026-08-05)

`DynamicTrayIconService` previously rounded every measured charge/discharge
rate to an integer before drawing it. A real rate between 0.1W and 0.9W was
therefore rendered as `0`, hiding the fact that power was flowing. The shared
`FormatWattageForIcon` formatter now keeps one decimal place below 1W while
preserving the existing integer and `99+` display rules at higher values.

Added `TrayIconPowerFormattingTests` covering sub-watt, whole-watt, and
overflow formatting. Released as part of v1.1.6.

## v1.1.4 released (latest)

Expand Down Expand Up @@ -131,10 +202,12 @@ run during this pass.

## Branch consolidation (2026-08-05)

After refreshing `origin`, every branch had no commits outside `main`. The
already-merged local `feat/show-version-and-handle-duplicate-launch` branch
and remote `claude/memory-cpu-optimization-p0z5ve` branch were deleted. Only `main` remains locally and on `origin`; the working-tree audit changes remain
uncommitted.
After refreshing `origin`, `codex/v1.1.5-release` was confirmed to have no
commits outside `main`; `main` already contained its release commit via the
`c9fdbed` merge. The local and `origin` copies of that already-merged branch
were deleted. Only `main` remains locally and on `origin`. The battery-voltage
history and sub-watt tray-formatting changes in the working tree remain
uncommitted and were preserved.

## Complete Traditional Chinese XML Documentation Pass (2026-08-05)

Expand Down
90 changes: 87 additions & 3 deletions MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,91 @@
</Grid>
</TabItem>

<!-- TAB 3: Smart Diagnostics -->
<!-- TAB 3: Battery Voltage by State of Charge (0–100%) -->
<TabItem x:Name="TabVoltageHistory" Header=" 🔋 電池電壓紀錄 ">
<Grid Margin="0,8,0,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="270"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>

<Grid Grid.Row="0" Margin="0,0,0,7">
<StackPanel>
<TextBlock x:Name="TxtVoltageHistoryTitle" Text="🔋 電池電壓紀錄(0–100%)" FontSize="13" FontWeight="Bold" Foreground="{StaticResource TextMain}"/>
<TextBlock x:Name="TxtVoltageHistorySummary" Text="已記錄 0 / 101 個電量區間,累積 0 次電量變化事件" FontSize="11" Foreground="{StaticResource TextMuted}" Margin="0,3,0,0"/>
<TextBlock x:Name="TxtVoltageHistoryHint" Text="僅顯示電池驅動回報的實測電壓;同一電量未變化時不重複記錄。" FontSize="11" Foreground="{StaticResource TextMuted}" Margin="0,2,0,0"/>
</StackPanel>
<Button x:Name="BtnClearVoltageHistory" Content="清除電壓紀錄" Style="{StaticResource SecondaryButtonStyle}" HorizontalAlignment="Right" VerticalAlignment="Center" Padding="10,4" Click="BtnClearVoltageHistory_Click"/>
</Grid>

<Border Grid.Row="1" Background="#182338" BorderBrush="{StaticResource BorderColor}" BorderThickness="1" CornerRadius="8" Padding="11,9" Margin="0,0,0,7">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>

<Border Grid.Row="0" Background="#060910" BorderBrush="#1E293B" BorderThickness="1" CornerRadius="6" ClipToBounds="True">
<Grid x:Name="GridVoltageChartContainer" SizeChanged="GridVoltageChartContainer_SizeChanged">
<Canvas x:Name="CanvasVoltageGridlines"/>

<Grid VerticalAlignment="Stretch" HorizontalAlignment="Left" Margin="8,4,0,4" IsHitTestVisible="False">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock x:Name="TxtVoltageYAxis100" Grid.Row="0" Text="-- V" FontSize="10" Foreground="#94A3B8" FontFamily="Consolas, Segoe UI" VerticalAlignment="Top"/>
<TextBlock x:Name="TxtVoltageYAxis75" Grid.Row="1" Text="-- V" FontSize="10" Foreground="#94A3B8" FontFamily="Consolas, Segoe UI" VerticalAlignment="Top"/>
<TextBlock x:Name="TxtVoltageYAxis50" Grid.Row="2" Text="-- V" FontSize="10" Foreground="#94A3B8" FontFamily="Consolas, Segoe UI" VerticalAlignment="Top"/>
<TextBlock x:Name="TxtVoltageYAxis25" Grid.Row="3" Text="-- V" FontSize="10" Foreground="#94A3B8" FontFamily="Consolas, Segoe UI" VerticalAlignment="Top"/>
<TextBlock x:Name="TxtVoltageYAxis0" Grid.Row="4" Text="-- V" FontSize="10" Foreground="#94A3B8" FontFamily="Consolas, Segoe UI" VerticalAlignment="Bottom"/>
</Grid>

<!-- Segments are split at missing percentages, so the line never
implies a voltage that has not actually been observed. -->
<Canvas x:Name="CanvasVoltageLines" IsHitTestVisible="False"/>
<Canvas x:Name="CanvasVoltageMarkers" IsHitTestVisible="False"/>
</Grid>
</Border>

<!-- Half-width end columns so 25% / 50% / 75% land on their true
positions: five equal columns would centre them at 30% and 70%. -->
<Grid Grid.Row="1" Margin="44,3,8,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.5*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="0.5*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="0%" Grid.Column="0" FontSize="10" Foreground="{StaticResource TextMuted}" HorizontalAlignment="Left"/>
<TextBlock Text="25%" Grid.Column="1" FontSize="10" Foreground="{StaticResource TextMuted}" HorizontalAlignment="Center"/>
<TextBlock Text="50%" Grid.Column="2" FontSize="10" Foreground="{StaticResource TextMuted}" HorizontalAlignment="Center"/>
<TextBlock Text="75%" Grid.Column="3" FontSize="10" Foreground="{StaticResource TextMuted}" HorizontalAlignment="Center"/>
<TextBlock Text="100%" Grid.Column="4" FontSize="10" Foreground="{StaticResource TextMuted}" HorizontalAlignment="Right"/>
</Grid>
</Grid>
</Border>

<ListView x:Name="LvBatteryVoltageHistory" Grid.Row="2" Background="#0C1322" BorderBrush="{StaticResource BorderColor}" BorderThickness="1" Foreground="{StaticResource TextMain}">
<ListView.View>
<GridView>
<GridViewColumn x:Name="VoltagePercentColumn" Header="電量 (%)" Width="100" DisplayMemberBinding="{Binding BatteryPercentText}"/>
<GridViewColumn x:Name="VoltageAverageColumn" Header="平均電壓" Width="120" DisplayMemberBinding="{Binding AverageVoltageText}"/>
<GridViewColumn x:Name="VoltageRangeColumn" Header="實測範圍" Width="150" DisplayMemberBinding="{Binding VoltageRangeText}"/>
<GridViewColumn x:Name="VoltageSamplesColumn" Header="變化次數" Width="100" DisplayMemberBinding="{Binding SampleCount}"/>
<GridViewColumn x:Name="VoltageLastColumn" Header="最後紀錄" Width="170" DisplayMemberBinding="{Binding LastRecordedAtText}"/>
</GridView>
</ListView.View>
</ListView>
</Grid>
</TabItem>

<!-- TAB 4: Smart Diagnostics -->
<TabItem x:Name="TabDiagnostics" Header=" 💡 智慧診斷與維護建議 ">
<ScrollViewer VerticalScrollBarVisibility="Auto" Margin="0,8,0,0">
<ItemsControl x:Name="IcDiagnostics">
Expand All @@ -523,7 +607,7 @@
</ScrollViewer>
</TabItem>

<!-- TAB 4: Life Estimates -->
<!-- TAB 5: Life Estimates -->
<TabItem x:Name="TabLifeEstimates" Header=" ⏱️ 續航估算表 ">
<ListView x:Name="LvLifeEstimates" Background="#0C1322" BorderBrush="{StaticResource BorderColor}" BorderThickness="1" Foreground="{StaticResource TextMain}" Margin="0,8,0,0">
<ListView.View>
Expand All @@ -536,7 +620,7 @@
</ListView>
</TabItem>

<!-- TAB 5: Recent Usage -->
<!-- TAB 6: Recent Usage -->
<TabItem x:Name="TabRecentUsage" Header=" 📊 最近使用紀錄 ">
<ListView x:Name="LvRecentUsage" Background="#0C1322" BorderBrush="{StaticResource BorderColor}" BorderThickness="1" Foreground="{StaticResource TextMain}" Margin="0,8,0,0">
<ListView.View>
Expand Down
Loading
Loading