bfa0b537eee7f7d82a788b861fba9cfe6f358804
4
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
bfa0b537ee |
i18n: ship the UI in English and German
ci / build-test (push) Failing after 35s
The last open item on the M7 list. Number and currency formatting was already locale-aware, but every string in the UI was an English literal, so a German instance read half in each language -- German data, English chrome. This translates all of it and adds the machinery to keep it translated. Strings live in Localization/Strings.resx (English, neutral) and Strings.de.resx. The neutral file generates a strongly-typed accessor at build time, aliased as S in _Imports.razor, so components reference compiled properties -- @S.Common_Save, not a string key. That choice is the point: across 4,500 lines of markup, a key lookup that silently falls back to its own name is a defect you find in production, while a renamed property is a build error. Generation runs in MSBuild rather than the IDE designer, so dotnet build alone reproduces it anywhere. Resource fallback is the hazard here. Ask for a key the German satellite lacks and ResourceManager quietly serves the English one -- correct at runtime, disastrous at release time, because a half-translated build looks perfectly healthy. StringResourceTests reads each satellite with tryParents: false, which is the only way to see what one actually contains, and fails on a missing or blank translation, a placeholder that changed arity, an orphan, or a key nothing references. Three things needed more than substitution: - Domain enums reached the screen as bare identifiers. They stay bare in the model -- they are persisted as text and appear in the REST API, so their names are part of the data contract -- and DisplayNames is now the single place that decides how each value is spoken. Every arm ends in a fallback returning the identifier, so a value added later cannot throw mid-render; EnumDisplayNameTests is what stops that safety net quietly becoming the shipping behaviour. - Infrastructure was writing display text: FlowService's "Other (X)", MeterPeriodView's "Generation"/"Consumption", the HA connection-test verdicts, the updater's snackbar, the CSV importer's row warnings. Each now returns an outcome value and the UI supplies the words, which is where the reader's language is known. Diagnostics that are not ours -- an HTTP status, systemd's stderr, an exception message -- are passed through untranslated, and every English summary is kept alongside the outcome so log lines never move with the UI language. The UpdateRunner change is additive only; no gate was touched. - Importer warnings carry their arguments rather than a finished sentence, so the numbers inside them pick up the reader's grouping. A register that reads 2.940,19 everywhere else must not read 2940.19 only inside a warning. Switching language is a redirect through /culture/set followed by a full reload, not an interactive state change: a Blazor Server circuit is fixed to the culture of the request that opened it. That makes the endpoint a redirector taking its target from the query string, so anything but a local path is refused rather than followed. Preference order is the cookie, then Accept-Language, then MeterVault__Locale -- an instance can be pinned to one language and a reader can still switch. Locale keeps its documented default of "en". Format now follows CurrentCulture instead of a hardcoded de-DE, so an instance with nothing configured and a browser asking for English will show English number formatting where it previously showed German; set MeterVault__Locale=de to pin the old behaviour. The importer's de-DE parsing is untouched and stays that way -- that dialect is a property of the spreadsheets, not of whoever is looking at the dashboard. Anything that comes from the database -- meter names, energy-type display names, category names -- is user data and is never translated. Claude-Session: https://claude.ai/code/session_0112ezeWqaZ85kTj5bYu9JHx |
||
|
|
cf7e0396f0 |
Dashboard: report when a newer release is available
ci / build-test (push) Successful in 1m13s
The instance had no idea what version it was: VERSION drives tagging and the image publish, but was never stamped into the assemblies, so a running build reported 1.0.0 forever. Directory.Build.props now stamps it into every project. The dashboard compares that against the newest tag in the source repository and shows a banner when behind. A plain GET of a public tag list -- nothing about the instance is sent -- cached six hours, failing quiet. Two things it deliberately does not do. It never blocks a render: the banner paints from the cached answer and refreshes after first render, so a cold start or an unreachable repository costs nothing rather than holding the dashboard open for an HTTP timeout. And it never guesses: an unknown version on either side shows no banner at all, because a banner that cannot clear trains people to ignore the next real one. Version comparison is numeric on exactly three components, not System.Version and not string order. Tags are written vX.Y.Z, the assembly reports X.Y.Z with a +commithash suffix, and "0.10.0" sorts below "0.9.0" as a string -- each of those is a way the banner sticks or never appears. Prerelease suffixes compare equal to their release so an rc tag does not nag. Gitea does not promise semver ordering, so the highest tag wins rather than the first. The command shown depends on the install: the LXC has `update`, a container is replaced by pulling an image, and telling container users to run `update` sends them after a command that does not exist. No update *button*. The UI has no authentication and the LXC runs the app as root, and `update` builds whatever is on master, so a click would be an unauthenticated path to arbitrary code execution for anything on the LAN. The README now states the no-auth position plainly rather than leaving it implied. Tests cover the parse and ordering cases that would strand a banner, the Gitea payload shape captured from the live API, unreachable and garbage responses, and that the VERSION file actually reaches the assembly -- read from MeterVault's own assembly rather than GetEntryAssembly(), which under `dotnet test` is the test host and reported a confident wrong answer. The suite makes no outbound request: the app factory disables the check. Claude-Session: https://claude.ai/code/session_01V6joyergfvVLFEizH1hJLd |
||
|
|
85d650a8f5 |
Fix: enable global interactivity so dialogs/selects actually work
ci / build-test (push) Successful in 1m12s
Editing a meter (any admin dialog, select dropdown, snackbar, dark-mode toggle) did nothing: pages declared @rendermode InteractiveServer individually, but MainLayout — which hosts MudDialogProvider/MudPopoverProvider/MudSnackbarProvider — was rendered by <Routes>, which was static. Inline MudDialogs and MudSelect popovers render through those providers, so with the providers non-interactive no dialog could ever open. Set the render mode on <Routes> and <HeadOutlet> in App.razor (global interactivity) and removed the now-redundant per-page @rendermode declarations (they would otherwise throw "parent already has a render mode"). Why it slipped through: the integration render tests only issue a GET (static prerender), which never exercises the SignalR circuit. Verified this fix with a real headless-browser run (Playwright): the meter edit dialog opens and the "Sub-meter of (upstream meters)" multi-select opens with options — proving the layout providers are now interactive. 116 tests still green. Claude-Session: https://claude.ai/code/session_01Lz2RqAsnQhetqWNoCDfexK |
||
|
|
d5419729e5 |
M5: Blazor dashboard (MudBlazor + ApexCharts)
- MudBlazor theme (dark default) + responsive drawer/appbar layout + nav. - DashboardService read model: KPIs with period-over-period deltas, category breakdown, "what cost more/less" difference view, monthly trends. - Pages: Overview (KPI cards + DeltaChip + donut + difference table), Trends (range-select bar chart), Meters (list + source status), Import (load reference dataset + CSV dry-run preview), Admin (energy types, tariffs). Charts isolated into components to avoid the ApexCharts/MudBlazor Color/Format name clashes. - ReferenceDataImporter: one-click load of all four sheets as a starter dataset (meters, tank, tariff history, category memberships) — bundled sample CSVs copied to app output. - End-to-end render test: import creates meters + consumption; overview/meters/trends/ import/admin pages all return 200 with KPI cards rendered. 92 tests green (56 Core + 36 integration). Deferred to polish: dedicated PV & oil/consumable panels, meter-detail page, full admin CRUD, prev-year trend overlay. Claude-Session: https://claude.ai/code/session_01WujdMtMJPbxDpDnMeK22rr |