App UI development - poli0981/frameledger GitHub Wiki
The screens are specified, and described as built, in docs/08_UI.md; the rules for the UI library
are docs/16_WPFUI_SYNTAX.md. The App is src/FrameLedger.App: WPF on .NET 10 with
WPF UI 4.3.0 (pinned), CommunityToolkit.Mvvm, ScottPlot for charts and Markdig for documents.
- A .NET Generic Host with dependency injection. Pages and their view models are transient — a page is made again on
every navigation, so state lives in the view models' services and the database, never in a page. A view model that
listens to a service attaches on its page's Loaded and detaches on Unloaded, and is never
IDisposable: the container keeps every transient disposable it makes until the App exits. - The ledger's reads run on the thread that calls them (Microsoft.Data.Sqlite completes its asynchronous calls synchronously): anything heavy after one — decoding a session, sorting its frames — is put on the thread pool.
- Navigation goes only through WPF UI's
INavigationService; a page implementsINavigableView<TViewModel>. - The App opens
ledger.dbitself to read; anything that acts on the PC — hooking, deleting sessions, pausing — is a request to the agent over the pipe. -
FrameLedger.exe --data-dir <copy>is a viewer: no agent, no update, the controls that would change the PC disabled.
Every user-visible string comes from .resx, in English, Vietnamese and Japanese (src/FrameLedger.App/Strings.resx
and its .vi and .ja siblings; the shared ones are in src/FrameLedger.Shared). tools/resx-gen.ps1 writes the
accessor class, and tools/resx-audit.ps1 fails the build when a key is missing in a language. The safety texts in
Japanese wait for a human reviewer, never a machine draft (docs/09_I18N.md).
Colours come only from WPF UI's theme brushes, by DynamicResource — never a hex value, never StaticResource for a
theme brush. Muted text uses the App's SecondaryText and TertiaryText styles (src/FrameLedger.App/Styles/FrameLedger.xaml),
and every page sets the theme's text colour on its root.
The chart palettes are the one place a colour is written out, once per theme. The game page's artwork band is the one
colour worked out in code: the artwork's own, as strongly as every text on it still reads, recomputed when the theme
changes.
Each of these cost a release; the full list, with the tests that hold each, is §Gotchas checklist of
docs/16_WPFUI_SYNTAX.md.
-
ui:TextBlock Appearance=looks its brush up once: a theme switch leaves the text in the old theme's colour. - A page is shown in a
Frame, which passes it no text colour: a text that sets none is black, on the dark theme too. - A class handler on
Loadeddoes not reach every element: WPF raises Loaded only where a subtree listens for it. - Every page sets
ScrollViewer.CanContentScroll="False", or WPF UI wraps it in a scroller that eats the mouse wheel; a scroller inside a dialog is aui:PassiveScrollViewer. - A scroll bar is hidden through its scroller's own
ScrollBarVisibility, never a style on the bar. -
ui:InfoBardraws no content (the App's copy of its template does);ui:Badgepalette appearances are not theme colours; WPF UI's DataGrid parts have keyed styles only; its CheckBox is 120 px wide at least. - No dialog closes on Esc by itself (
Services/DialogKeyboarddoes it); ScottPlot zooms on every wheel unless told not to. - Apply the theme before the first window loads; watch the system theme only once the window has a handle.
tests/FrameLedger.App.Tests renders every page, window and dialog under the real theme dictionaries on one STA thread
(PagesLoadTests.OnStaAsync): contrast in both themes, a theme switch while a surface is on screen, scroll bars with the
option on in a real window, keyboard and automation names. Each surface is hosted as the App hosts it — a page in WPF UI's
navigation presenter, not straight in a window. A rendered check is run red against the unfixed XAML first.