Fix/mac prerelease universal rids - #373
Closed
SteveTeece wants to merge 83 commits into
Closed
SteveTeece wants to merge 83 commits into
SteveTeece wants to merge 83 commits into
Conversation
Feature 027 introduces per-language resource files (Australian English baseline) so every screen, report, menu, tile, exception message and user-facing enum value reads its text via IStringLocalizer, plus a persisted, OS-aware Settings.LanguageCode selection. Adds the full spec-kit artifact set: - spec.md - 3 prioritized user stories, FR-001..FR-024, SC-001..SC-010 - plan.md, research.md - IStringLocalizer + ~12 area .resx markers, missing-key logging decorator, MoneyFormatter (fixed AUD), startup culture ladder (explicit -> OS language -> en-AU) - data-model.md - Settings.LanguageCode column + migration outline - contracts/ - localization interfaces + resource-key catalog/guards - quickstart.md, checklists/requirements.md - tasks.md - 64 tasks (T001-T064) in 6 wave-grouped phases Bumps .specify/feature.json to specs/027-localization-support. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Phase 1 (Setup): - Add Microsoft.Extensions.Localization 10.0.10 to Directory.Packages.props - Scaffold StageFright.Localization.Tests guard-test project (refs Core/UI/Reports) - Add version-less PackageReference + NeutralLanguage=en-AU to Core/Reports/UI csproj Phase 2 (Foundational): - 12 area resource marker classes + empty neutral .resx: NavigationResource, ValidationResource, EnumsResource (Core); ReportsResource (Reports); Shared/Dashboard/Members/Rehearsals/Events/Finance/Settings/SetupResource (UI) - ILocalizer facade + Localizer impl (Get/Get-with-args/Plural/Enum), with named-placeholder substitution by order of first appearance (FR-010) - MissingKeyLoggingLocalizerFactory: IStringLocalizerFactory decorator that logs a Warning and falls back to neutral on a missing key (FR-008/FR-009) - EnumLocalizationExtensions.LocalizeEnum: Enum_<Type>_<Member> lookup via a factory holder set once at startup (FR-024) - MoneyFormatter.Format/FormatWithCode: fixed "$"/"AUD" symbol, culture-driven separators/grouping (FR-015) - Guard-test scanning infrastructure (LocalizerKeyUsageScanner, ResxKeyScanner, PlaceholderTokenScanner, UserFacingEnumScanner) — no assertions yet - LocalizedTestContext bUnit base wiring AddLocalization() + LocalizeEnum - DI wiring in MauiProgram: AddLocalization(), decorated IStringLocalizerFactory, AddScoped<ILocalizer, Localizer>(), EnumLocalizationExtensions.UseFactory at startup Full solution build: 0 warnings, 0 errors. Full test suite: 1693 existing tests pass unchanged. User stories 1-3 and Polish (T015-T064) remain undone. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Phase 3 / User Story 1 — the extraction pattern proven end-to-end on the navigation shell plus the complete Members module. Same en-AU wording, now sourced from IStringLocalizer<T> resources. Resources (T017-T021): - NavigationResource / MembersResource / ValidationResource / EnumsResource / SharedResource .resx populated with verbatim en-AU entries (labels, headings, buttons, placeholders, aria-label/alt/title, plural pair, enum display text). Call sites (T022-T028): - ShellLayout, ThemeProvider (no-op — no user text), MemberMenuItemProvider, MemberValidationService, MemberList/MemberDetail/MemberForm (.razor+.razor.cs), MembersTile, MembersDashboardTileProvider read text via IStringLocalizer. - MemberStatus + Theme render through LocalizeEnum() (FR-024); fee amounts via MoneyFormatter.Format (FR-015); parameterised strings via ILocalizer helper methods in code-behind. - Completed the T011 gap: @using StageFright.Core.Localization in _Imports.razor. - DI unchanged — all three localizer-taking registrations resolve from AddLocalization() + the decorating factory. Tests (T015-T016): - New StageFright.Localization.Tests US1 guard suite: baseline completeness (+plural pairing), MemberStatus/Theme enum-key coverage, residual-literal scan over the US1 slice, no-raw-enum-render, no-"C"-currency-format, missing-key warning + neutral fallback. - New FR-018 bUnit tests assert US1 text resolves via resource keys / LocalizeEnum. - 5 existing bUnit classes moved to LocalizedTestContext; it now also registers ILocalizer. StubStringLocalizer / RealStringLocalizer test helpers added for the constructor-injection sites. Full build clean; 1706 tests pass (Localization 6, Reports 178, Core 597, UI 587, Data 152, Integration 186). US2/US3/Polish not started. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Move every user-facing literal in Pages/Rehearsals/** into
RehearsalsResource.resx (en-AU verbatim) read through IStringLocalizer
at render time: RehearsalList, RehearsalForm and AttendanceGrid .razor
+ .razor.cs, including aria-label/placeholder text (FR-001). The
attendance fee now formats through MoneyFormatter, not ToString("C")
(FR-015). Parameterised strings (search term, dates, member names,
attendance rate) resolve via ILocalizer.Get in code-behind helpers.
RehearsalFormModel.Date drops its dead DataAnnotations ErrorMessage
literal (RequiredAttribute never fires on a non-nullable DateTime).
Three catch blocks stop surfacing raw ex.Message, matching the US1
MemberForm precedent.
Tests: new Rehearsals-scoped guard suite (baseline completeness,
residual-literal incl. accessibility attrs, no-"C"-currency) as an
interim stand-in for the still-pending repo-wide T029/T030; new
FR-018 render tests; RehearsalListTests + AttendanceGridTests flipped
to LocalizedTestContext (resx values byte-identical to prior copy).
Full rebuild clean (0 warnings). 1714 tests pass (+8 from the US1
baseline). 29/64 tasks; US2 continues with T029/T030 + T031/T033-T045.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Dashboard page chrome, the six remaining dashboard tile bodies (Events,
Rehearsals, AttendanceTrend, Finance, CashFlow, OutstandingBalances) and
their tile providers now read every user-facing string from
DashboardResource / EventsResource / FinanceResource / RehearsalsResource
/ SharedResource. Chart dataset legend labels localized; tile fee/balance
amounts moved from .ToString("C") to MoneyFormatter (FR-015). Tile
providers ctor-inject IStringLocalizer<T> (matching MembersDashboardTile
provider). bUnit tile/dashboard test classes flipped to
LocalizedTestContext; provider-test + V8 integration ctors updated.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…eys (US2, T038 + T042 groundwork) All six navigation menu providers (Dashboard, Rehearsals, Events, Finance, Settings, Reports) ctor-inject IStringLocalizer<NavigationResource> and source Title/ShortLabel/sub-item labels from NavigationResource.resx (matching MemberMenuItemProvider). EnumsResource.resx gains Enum_<Type>_<Member> keys for FeeType, PaymentMethod, PaymentType, AccountType, TaxCode, ReconciliationStatus and JournalEntryType (call-site LocalizeEnum swaps land with each module + T042). Core.Tests gains a RealStringLocalizer fixture; the two affected menu-provider test ctors updated. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
AddAccountForm, BorderedListBox, OpeningBalanceEntryForm, ReactivationForgivenessDialog, ReportViewer and TileRenderer now read all user-facing text (labels, headings, buttons, placeholders, aria-labels, status/error messages, plural button labels) from SharedResource.resx. AccountType options and account-type column render via LocalizeEnum; forgiveness fee amounts and report money via MoneyFormatter; parameterised strings resolved in code-behind helpers. SubmitButtonText / EmptyText params made nullable with a localized fallback. 16 bUnit test classes that render these components flipped to LocalizedTestContext. Full suite green (1714). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
All 10 Events/AGM screens (EventList, EventForm, EventDetail, ParticipationGrid, AgmList, AgmDetail, ScheduleAgm, RecordAgm, RecordSpecialElection, AgmAttendanceGrid) plus AgmAttendanceReportPrinter read every user-facing string, aria-label, badge, status text, validation and print message from EventsResource.resx / SharedResource. Parameterised strings via ILocalizer code-behind helpers; EventFormModel data-annotation messages dropped to bare [Required] (dead on non-nullable value types, matching US1). 8 Events bUnit test classes flipped to LocalizedTestContext. Full suite green. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
SettingsPage tab titles/error-boundary text and the General, Sales Tax, Committee, Event Types and Backup & Restore tabs read all user-facing strings, labels, help text, badges, aria-labels, status/error/success messages and validation from SettingsResource.resx / SharedResource. Theme label via LocalizeEnum; TaxCode options via LocalizeEnum (value stays the invariant token); audit-retention option pluralised via ILocalizer.Plural; fee input adornments fixed to "$" per FR-015 (was culture CurrencySymbol). Language picker row deliberately left for US3/T056. 5 Settings bUnit test classes flipped to LocalizedTestContext. Full suite green. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
SetupWizard shell + all 9 tab components (GeneralAppearance, MembershipFees, ThemeSelection, SalesTax, SampleData, ChartOfAccounts, OpeningBalances, Committee, Review) read every user-facing string, label, help text, placeholder, aria-label, badge and validation/error message from SetupResource.resx. Review money via MoneyFormatter; Theme + TaxCode via LocalizeEnum (wizard's Light/Dark <select> keeps invariant option values per spec 017 FR-022); audit-retention & review lines pluralised via ILocalizer.Plural; fee adornments fixed to "$". Language step deliberately left for US3/T057. 5 Setup tab bUnit test classes flipped to LocalizedTestContext. Full suite green. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
StartupError.razor (database recovery page) and ReportsPage.razor read their headings, body text, buttons, prompts and error messages from SharedResource.resx. MainPage.xaml / App.xaml carry no user-facing text. Remaining Pages/** (Finance) is T034. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Move every user-facing literal in the 13 Finance pages + code-behinds
into FinanceResource.resx (en-AU baseline). Fee/payment/account/tax/
reconciliation-status enums render via LocalizeEnum; every displayed
amount goes through MoneyFormatter (FR-015) instead of ToString("C") /
FormatString="{0:C}". aria-labels, placeholders and the embedded-link
"no accounts configured" warnings are localized too (FR-001).
Nine Finance bUnit test classes flipped to LocalizedTestContext so
IStringLocalizer<FinanceResource> resolves; neutral values are
byte-identical to the prior hardcoded copy so assertions stay green.
Full solution build clean; full suite 1714 passing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… groundwork)
The US1-deferred piece of T041: AgeCalculationService.ValidateDateOfBirth
now sources its three user-facing messages from ValidationResource via the
ILocalizer facade (named {Age}/{MaxYears}/{MinimumAge} placeholders), so the
service is DI-constructed with ILocalizer.
Call-site fan-out handled: MemberList/MemberDetail switch from `new()` to an
injected AgeCalculationService (registered in LocalizedTestContext for bUnit);
Core/Integration/Reports test projects each gain a RealLocalizer fixture and
pass it to the ~12 `new AgeCalculationService(...)` sites.
The remaining T041 work — service-thrown ValidationException/DomainValidation-
Exception message text across ~18 Core services — is not in this commit.
Full solution build clean; full suite 1714 passing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Route every report provider's user-facing text through the ILocalizer
facade against a new populated ReportsResource.resx (~120 en-AU entries,
transcribed verbatim per FR-004): report names, filter labels,
titles/subtitles (named-placeholder formatted), section headings, column
headers, subtotal/total + inline row labels, and the GL-imbalance
exception message. Enum-valued cells (MemberStatus, ReconciliationStatus)
resolve via ILocalizer.Enum (FR-024).
Filter option VALUES stay culture-invariant tokens (Options, unchanged —
still compared in GenerateAsync and persisted); the localised option
LABEL is carried on a new parallel ReportFilterDefinition.OptionLabels
list, rendered by ReportViewer with fallback to the token.
Report amounts keep decimal.ToString("F2") — providers never used "C",
and adding a $ symbol/grouping would break SC-002 render parity. Report
renderers (T040) and service exception text (T041) remain.
T030: ReportsResourceLabelTests (names/headers/section+total labels
resolve via ReportsResource, no raw-key/blank leak — FR-006) and
PluginTextNonEnglishCultureTests (TestPlugin English report + tile text
renders unchanged under fr-FR, run on a dedicated thread so the culture
never leaks into parallel tests — FR-020).
Build 0 warnings / 0 errors; full suite 1733 pass / 0 fail
(Reports 178->195, Integration 186->188).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…rency sweeps (T042, T044)
T040: all 6 PDF report renderers now source their page chrome from
ReportsResource.resx (~21 en-AU keys, verbatim per FR-004):
- PdfReportRenderer: "Generated: {DateTime} UTC", footer "Page"/"of"
- AttendanceRollPdfRenderer: title, "Rehearsal: {Date} at {Time}", Name/Present/Pd headers
- AgmResultsPdfRenderer: title, meeting/attendance lines, "Elected Positions",
"No positions recorded.", bold "{Label}: " position prefix, "General Committee Member:"
- CheckboxSheetPdfBuilder: shared "Name" column + footer; gains an ILocalizer param
and a nameColumnHeader arg threaded to BuildMemberTable
- Event/AgmAttendanceSheetPdfRenderer: titles, date lines, Participated/Attended headers
The 5 concrete renderers ctor-inject ILocalizer (resolves from the existing
AddScoped<ILocalizer, Localizer>() - no MauiProgram change). CsvReportExporter has
no user-facing literals and is untouched. 8 direct test-construction sites now pass
RealLocalizer.Instance; ReportsResourceLabelTests gains 22 chrome-key + placeholder
assertions (FR-006).
T042: verified the user-facing enum sweep is complete - EnumsResource.resx holds
every Enum_<Type>_<Member> key for all 9 user-facing enums. Fixed the one residual
raw display site (the item deferred from US1): MemberDetail Fee Payment History
FeeType column now uses fee.FeeType.LocalizeEnum().
T044: verified zero decimal.ToString("C")/{0:C}/FormatString display sites remain in
UI or Reports. The one user-facing residual - BankReconciliationService.cs:153
ReconciliationException message - is exception-message text owned by T041.
Full solution build: 0 warnings / 0 errors. Full test suite: 1755 passed / 0 failed
(Reports 217, up from 195).
US2 remaining: T041 (user-facing exception Message text across ~18 Core services -
high blast radius, own session), T045 (DI recheck after T041's ctor changes),
T029 (repo-wide guard suite - blocked until T041).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… part 1)
Route user-facing ValidationException / DataIntegrityException / ImportException
Message text through ValidationResource for the eight non-Finance Core services:
AgmService, EventService, EventTypeService, CommitteeOfficeHolderTypeService,
AttendanceService, SettingsService, SetupService, BackupService.
- 34 new en-AU keys in ValidationResource.resx (verbatim wording, FR-004);
named-placeholder keys for {Year}/{SchemaVersion}/{SupportedMajorVersion}/{EntityType}.
- Each service takes the ILocalizer facade via a required ctor param (matches the
AgeCalculationService T041-groundwork precedent; only path that substitutes named
tokens). All eight are plain AddScoped<I,Impl> registrations so DI auto-resolves
the new param -- no MauiProgram change.
- BackupService DeserializeAndValidate / ValidateVersion / ValidateCompleteness
change from private static to instance to read _localizer.
- EntityNotFoundException built-in message and DataAccessException wrappers stay
English (diagnostic per FR-007 -- UI never shows them verbatim).
- 20 test call-sites updated to pass RealLocalizer.Instance; new RealLocalizer
fixture added to StageFright.Data.Tests.
- Interim guard Us2ExceptionMessageGuardTests (baseline completeness + no residual
literal) scoped to the eight files.
Full build 0 warnings / 0 errors. Full suite 1757 pass.
Remaining for US2: T041 Finance services (incl. BankReconciliationService {Difference:C}),
T045 DI recheck, T029 repo-wide guard suite.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…de guard (T041, T029, T045)
T041 (Finance half): route user-facing ValidationException / ReconciliationException
Message text through ValidationResource for all 9 Finance services — AccountService,
BankReconciliationService, ExpensePaymentService, PaymentService, FeeService,
GeneralJournalService, IncomeEntryService, BankDepositService, OpeningBalanceService.
- 39 new verbatim en-AU ValidationResource keys (FR-004) + reuse of
Validation_Settings_NotConfigured; named-token keys for {Name} (Account duplicate),
{Date} (statement date), {Difference}/{Zero} (reconciliation finalise).
- BankReconciliationService finalise message: the {workspace.Difference:C} + literal
"$0.00" (the T044-flagged C-format residual) now routes both amounts through
MoneyFormatter.Format — byte-identical under en-AU, fixed-AUD under any culture (FR-015).
- Each service takes the ILocalizer facade via a required ctor param (matches the
non-Finance T041 precedent). All 9 are plain AddScoped<I,Impl> so DI auto-resolves
the new param — no MauiProgram change (T045).
- ~30 test ctor sites pass RealLocalizer.Instance (Core.Tests, Data.Tests,
Integration.Tests). EntityNotFoundException / GLBalanceException wrappers stay
English (diagnostic per FR-007).
- Us2ExceptionMessageGuardTests extended to cover the Finance services.
T029: new repo-wide Us2LocalizationGuardTests (8 facts) — baseline key completeness
across the US1+US2 surface, Enum_<Type>_<Member> coverage for all 9 user-facing enums
+ no-raw-enum-render, residual-literal scan over the UI display surface, orphan-satellite
keys, plural-pair + placeholder parity, cross-culture token parity, and no-"C"-currency
repo-wide. Report-provider filter option-VALUE tokens (guarded by ReportsResourceLabelTests,
T030) and DataAnnotations ErrorMessage / ValidationResult literals (compile-time constant,
resolved in T060) are documented exclusions.
resource-key-catalog.md §3 updated to reflect the implemented guard layout and exclusions.
Full rebuild 0 warnings / 0 errors. Full suite 1765 pass (Localization 19, Reports 217,
Core 597, UI 592, Data 152, Integration 188). US2 (Phase 4) complete — 45/64 tasks.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…-T059)
Adds the language-selection slice on top of the US1/US2 extraction:
- Settings.LanguageCode (nullable, presentation-only) + hand-written
EF migration AddLanguageCodeToSettings (dotnet ef can't build the MAUI
startup project here); SchemaVersion left at 1.1.0 (unchanged for the
last ~6 settings migrations; asserted verbatim by backup tests).
- ILanguageProvider FR-023 startup ladder (explicit choice -> OS display
language by exact then parent-language match -> en-AU), wired in
MauiProgram to set the process culture before the first Blazor render;
ISystemCultureProvider seam (SystemCultureProvider in the App layer).
- SupportedLanguagesCatalog: runtime discovery of shipped resource
cultures by scanning satellite folders for the StageFright assemblies;
qps-* pseudo-locales excluded; endonyms from CultureInfo.NativeName.
- Settings General tab + Setup Wizard language pickers (endonym <select>,
active-marked, inline restart notice at the point of change - FR-021);
choice flows through SetupRequest -> Settings at first-run Finish.
- CultureProvider cascading seam wrapping ShellLayout (parallel to
ThemeProvider; no v1 behaviour change) - named CultureProvider to
avoid colliding with the Core LanguageProvider service.
- qps-ploc test pseudo-locale: scripts/generate-pseudo-locale.py + 12
regenerable <Marker>.qps-ploc.resx fixtures (bracketed/accented,
{tokens} preserved, 3 keys omitted to exercise per-key fallback).
- Tests (+40): LanguageProvider ladder, catalog runtime discovery +
qps-* exclusion, migration round-trip, V21 startup/SC-006 no-data-
change, qps-ploc e2e, language-picker bUnit. LocalizedTestContext
registers the new language services.
Full rebuild: 0 warnings / 0 errors. Full suite: 1805 pass, 0 fail
(Localization 23, Core 620, Reports 217, UI 599, Data 154, Integration 192).
Polish (T060-T064) remains.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…cs (T060-T064)
T060: fold the interim per-slice residual-literal scans into one repo-wide guard
(Us2LocalizationGuardTests.Should_HaveNoUserFacingLiteral_When_AppSurfaceScanned)
covering all of StageFright.UI + every user-facing exception Message literal in
StageFright.Core. The widened scan flagged App.razor (project root, outside the old
4-dir scope): localized its error-boundary + not-found chrome to 6 new Shared_App_*
keys (neutral + qps-ploc). DataAnnotations ErrorMessage args are frozen as the one
documented carve-out.
T061: docs/localization/adding-a-language.md — translator/maintainer guide (FR-022, SC-009).
T062: CLAUDE.md gains a Localization section + module-list entry + money-column data-grid
note; resource-key-catalog.md updated to past tense; spec 007 account-balance contract
gets a forward-note (its {0:C} columns moved to MoneyFormatter).
T063/T064: full rebuild 0 warn / 0 err; full suite 1805 pass (Localization 23).
Spec 027 status: completed (64/64 tasks).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Drop-in en-US satellite for all 12 area .resx files, translated from the
en-AU neutral baseline. The runtime SupportedLanguagesCatalog discovers it
automatically — no code change — so it now appears in Settings ▸ General
and the Setup Wizard language step.
Divergence from en-AU is spelling + vocabulary only (the two dialects are
otherwise identical across this corpus):
- organisation → organization (Settings/Setup labels, Tax Summary subtitle)
- finalise / finalised → finalize / finalized (Finance button, reconciliation
validation messages, ReconciliationStatus enum label)
- cheque → check (PaymentMethod enum label)
- cancelled → canceled (report-generation message)
- "financial year" → "fiscal year" (Settings + opening-balances wizard copy)
Keys, {named} placeholders and <comment> docs are byte-identical to neutral,
so the Us2 orphan-key / placeholder-parity guards pass.
Also make three previously culture-fragile bUnit assertions resolve their
expected text through the same IStringLocalizer the component uses, so they
hold under a US UI culture (GitHub CI runs windows-latest = en-US):
- ReconciliationWorkspaceTests: assert ReconciliationStatus.Finalised.LocalizeEnum()
- SetupWizardTests: assert the localized "Organisation Settings" tab title
- refresh the now-stale "v1 ships only en-AU" note in SupportedLanguagesCatalog
Full suite green under both the default culture and -culture en-US
(UI 599, Core 620, Data 154, Reports 217, Integration 192, Localization 23).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Full French translation of all 12 area .resx files, dropped in beside the
en-AU neutral baseline. SupportedLanguagesCatalog discovers fr-FR at runtime —
no code change — so "français (France)" now appears in Settings ▸ General and
the Setup Wizard language step, and an fr / fr-FR operating system resolves to
it on first run. Selecting it renders the whole app in French with French
number/date formatting; the currency stays "$" / "AUD" (MoneyFormatter).
- ~1,000 <value> strings translated; keys, {named} placeholders, _One/_Other
plural pairs and <comment> docs are byte-identical to neutral, so the Us2
orphan-key / placeholder-parity / plural-pairing guards pass.
- AGM → "AGA" (assemblée générale annuelle); accounting terms use standard
French (plan comptable, grand livre, bilan, compte de résultat, rapprochement
bancaire, produits/charges, capitaux propres, écriture de journal…).
- Embedded-HTML values keep their <a href> markup; significant leading/trailing
spaces preserved.
No test changes needed: the only test that deliberately runs under fr-FR
(PluginTextNonEnglishCultureTests) still passes, and every other test runs
under the machine culture (en-GB locally / en-US on CI) where the fr-FR
satellite is not loaded.
Full suite green under both the default culture and -culture en-US
(Core 620, Data 154, Reports 217, UI 599, Integration 192, Localization 23).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…, and tasks Feature 028 readies the finance module for community/amateur-theatre groups outside Australia, judged against universal double-entry and bookkeeping good practice (not formal IFRS/GAAP). Sourced from GitHub issue #341 and sub-issues #342-#352. Adds the full spec-kit artifact set: - spec.md - 10 prioritized user stories (currency, regional number entry, statement consistency, basis-of-accounting disclosure, bank reconciliation, prior-year period locks, financial-year start, audit-history retention, written policies, sales-tax i18n spike), FR-001..FR-033, SC-001..SC-013 - plan.md, research.md - configurable org currency replacing hard-coded AUD, culture-safe money parsing, period-lock service, report integrity checks + disclosures - data-model.md - currency/period-lock/policy fields + migration outline - contracts/ - currency-formatting, period-lock, reports, and settings-and-setup contracts - checklists/requirements.md - tasks.md - 94 tasks (T001-T094) across 13 phases Bumps .specify/feature.json to specs/028-international-accounting-standards. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…008)
Phase 1 baseline confirmed green (1805 tests). Phase 2 lays the shared
foundation every user-story phase builds on — no user-visible behaviour
change yet; an AUD dataset formats byte-identically.
- SupportedCurrency record + CurrencyCatalog (curated ISO 4217 seed set:
AUD/USD/EUR/GBP/NZD/CAD 2-digit, JPY 0-digit, KWD/BHD 3-digit) with
case-insensitive TryGet (false + Default, no throw) and Get (throws
ValidationException on an unknown code). Mirrors SupportedLanguagesCatalog.
- MoneyFormatter gains Configure(SupportedCurrency); Format/FormatWithCode
now use the configured symbol/code and MinorUnitDigits while grouping and
placement still follow CultureInfo.CurrentCulture. Falls back to
CurrencyCatalog.Default (AUD/$/2) before Configure is called.
- MauiProgram configures MoneyFormatter for Settings.CurrencyCode right
after the display culture is applied; failure leaves it on AUD default.
- Settings entity + EF config: add CurrencyCode ("AUD"),
FinancialYearStartDay (1), ClosedThroughDate (null); AuditRetentionYears
default 1 -> 5 (range 1-7 unchanged).
- Migration AddInternationalAccountingSettings: adds the three columns
(NOT NULL DEFAULT backfills the existing row), alters only the
AuditRetentionYears column default (no UpdateData, so a configured value
is preserved).
Build 0W/0E; all 1805 tests green.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…y display (T009-T028)
Phase 3 / User Story 1 (spec 028). Every screen, report, PDF and CSV amount now
renders in the organisation's configured currency (Settings.CurrencyCode, ISO 4217,
default AUD) with the right symbol and minor-unit precision; grouping/placement still
follow the active culture. An AUD dataset is byte-identical to the pre-028 output.
- TaxCalculator.SplitInclusive: optional minorUnitDigits (default 2); all callers pass
CurrencyCatalog.Get(settings.CurrencyCode).MinorUnitDigits.
- First-run setup: mandatory #setup-currency picker on GeneralAppearanceTab bound to
SetupFormModel.CurrencyCode; SetupService validates it against CurrencyCatalog
(Validation_Setup_CurrencyUnknown) and persists the normalised code.
- SettingsService.SaveAsync rejects a post-setup currency change
(Validation_Settings_CurrencyImmutable) — currency is fixed after setup.
- All StageFright.Reports providers + JournalEntryPage totals route money through
MoneyFormatter.Format instead of ToString("F2")/"N2".
- New resource keys (neutral + en-US + fr-FR): Validation_Setup_CurrencyUnknown,
Validation_Settings_CurrencyImmutable, Setup_General_CurrencyLabel/Help.
- Tests: CurrencyCatalogTests, MoneyFormatterTests, TaxCalculator 0/2/3-digit theory,
Setup/Settings currency cases, CurrencyPickerTests (bUnit), AudZeroDriftTests +
V28_CurrencyConfigurationTests (integration acceptance). MoneyFormatterState
collection serialises MoneyFormatter.Configure-sensitive integration tests.
- Updated ~30 pre-existing report/UI/integration assertions to MoneyFormatter.Format(...).
- Docs: CLAUDE.md Localization note + spec 027 superseding notes for the fixed-$/AUD prose.
Full rebuild: 0W/0E. Tests: 1868 pass (was 1805).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… inputs (T029-T035)
Phase 4 / User Story 2 (spec 028). The manual journal and opening-balance forms
hand-rolled decimal.TryParse(value, NumberStyles.Number, CultureInfo.CurrentCulture)
on the value of an <input type="number"> — which the browser always serialises
invariant ("1.50"). Under fr-FR / de-DE the period was read as a thousands
separator and 1.50 posted to the ledger as 150 (FR-007…FR-009 / live data bug).
- New MoneyInput.Parse(string?): invariant decimal.TryParse with
NumberStyles.AllowDecimalPoint | AllowLeadingSign; 0m for null/blank/unparseable.
The single shared money-entry parse helper.
- JournalEntryPage.ParseAmount and OpeningBalanceEntryForm.SetAmount now delegate
to MoneyInput.Parse; removed the CurrentCulture parse and the now-unused
System.Globalization using from both.
- Tests: MoneyInputTests (unit, fr-FR/de-DE/en-AU), JournalEntryPageLocaleTests +
OpeningBalanceEntryFormLocaleTests (bUnit — entered amount stored exactly),
MoneyInputGuardTests (repo-wide: no UI money field parses with CurrentCulture;
both handlers route through MoneyInput.Parse), V28_LocaleSafeMoneyEntryTests
(integration acceptance — real journal/opening-balance services over SQLite,
fr-FR/de-DE input stored exact to the cent and identical to en-AU).
Build: 0W/0E. Tests: 1895 pass (was 1868, +27).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
after_implement task-sync: 35/94 tasks complete, status stays implementing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
International accounting-practice readiness (spec 028), together with the spec 027 localization work it was branched from and which had not yet reached dev. spec 027 — full IStringLocalizer-based localization: en-AU baseline plus en-US and fr-FR resource sets, OS-aware persisted display language, and a repo-wide hard-coded-literal guard. spec 028 — configurable ISO 4217 currency driven through MoneyFormatter; locale-safe money entry; Balance Sheet / Trial Balance integrity guards; basis-of-accounting disclosure on financial statements; conventional bank reconciliation; closed-period lock; financial-year start as a first-run setup decision; audit-trail retention, coverage and purge-failure surfacing; tax-exclusive amount entry alongside tax-inclusive; sub-twelve-month first financial year labelled a part-year; recoverable input tax (2320) reclassified as a Balance Sheet asset. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… data seeder
The debug data seeder read Settings.OrganizationName / AnnualFee /
AttendanceFee from whatever the setup wizard captured. A coordinator who
left the fees at zero got a hollow sample dataset: SeedAnnualFeesAsync
was a no-op and the door-cash -> petty-cash -> bank sweep in
SeedRehearsalsAsync moved nothing.
SeedAsync now stamps a generated organisation name ("Clarence Valley
Community Choir") and fee schedule -- annual $120, per-rehearsal $2,
February membership renewal, October committee renewal, six-seat general
committee -- over the wizard inputs before it uses them, mirroring how
sample data already supplies its own accounts, opening balances and
committee/AGM history (spec 022). Currency, language and sales-tax
treatment stay as configured. The ISettingsService.SaveAsync audit entry
is suppressed by the enclosing AuditTrailSuppressionScope like every
other seeded write.
Updates the DebugDataSeeder class summary and the app-host living spec's
"Optional sample-data seeding" section.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The script deleted nothing: it targeted %AppData% (Roaming) with forward-slash separators, which del misparses as switches. The MAUI app writes stagefright.db to FileSystem.AppDataDirectory, which on the unpackaged Windows head resolves to %LOCALAPPDATA%\StageFright Community\com.stagefright.community\Data. Rewrite the script to: - use %LOCALAPPDATA% with backslash separators and quoted paths - remove the SQLite WAL sidecars (stagefright.db-wal / -shm) too - also clear a repo-root design_time.db left by "dotnet ef" runs - guard every delete with "if exist" and report whether anything went Also note the concrete Windows path and the script in docs/SETUP.md's "Reset the Database" section. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The Settings ▸ General and Setup Wizard display-language pickers listed only "English (Australia)" even though the app now ships en-US and fr-FR satellite resource sets (added on top of spec 027). Root cause: MauiProgram registered the catalog as `AddSingleton<ISupportedLanguagesCatalog, SupportedLanguagesCatalog>()`. Microsoft.Extensions.DependencyInjection selects the greediest resolvable constructor, so it picked `SupportedLanguagesCatalog(IEnumerable<string> resourceAssemblyNames)` — the test-only seam — and resolved the unregistered `IEnumerable<string>` as an *empty* sequence (not null). The constructor's `?? DefaultResourceAssemblyNames` guard only covered null, so `_resourceAssemblyNames` became `[]`, `ContainsOurSatellite` probed for nothing, no `<culture>/StageFright.*.resources.dll` folder ever matched, and only the hard-coded `en-AU` baseline survived. It was masked until en-US/fr-FR shipped because en-AU was previously the only set anyway; every test used `new SupportedLanguagesCatalog()` directly or a fake catalog, so none exercised the DI path. - MauiProgram: register with an explicit factory (`_ => new SupportedLanguagesCatalog()`) so the documented parameterless constructor is used. - SupportedLanguagesCatalog: the `(IEnumerable<string>)` constructor now treats an empty sequence like null and falls back to probing Core/UI/Reports — defence in depth against any DI/empty-list caller. - Tests: SupportedLanguagesCatalogTests gains an empty-list fallback case; StartupSequenceTests gains a DI-container regression that resolves the catalog the "natural" `AddSingleton<TService,TImpl>()` way and asserts en-US and fr-FR are discovered (both fail without the fix, reproducing the "en-AU only" list). - Refreshed three now-stale "en-AU is the only shipped set" comments in the picker area (GeneralSettingsTab, LanguagePickerRenderTests, V21_LocalizationStartupTests). Verified in the running MAUI app via CDP: the Setup Wizard language select now offers en-AU / en-US / fr-FR. Full rebuild 0W/0E; 2059 tests pass. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… seeding (#361) New spec-kit feature on branch 029-first-run-language-seed. Addresses issue #361 (a saved language change is invisible until restart, with no prompt) and folds in a requested extension: move the Debug-only sample-data choice onto the same pre-wizard screen and strip both the language and sample-data steps out of the setup wizard. Scope captured in spec.md: - P1: a dedicated first-run language screen before the setup wizard; the choice is persisted outside the database and applied by one automatic restart. - P2: Settings language change shows a post-save "restart now / later" dialog instead of the transient inline notice. - P3 (Debug only): a "load sample data" option on the first-run screen that seeds before restarting and lands on the dashboard, wizard skipped. - Setup wizard loses its language selector, its sample-data option, and the tab-bypass behaviour that only existed to support in-wizard seeding. - Startup language resolution gains a stored-preference tier between Settings.LanguageCode and the OS language. - New app self-restart capability (Windows; graceful no-op elsewhere). Supersedes the first-run-language parts of spec 027 and the in-wizard sample-data parts of specs 017/022. No DB schema change; no GL/money change. Requirements-quality checklist passes; two multi-interpretation decisions (auto-restart into the wizard; replace the inline notice with a dialog) were settled with the requester and recorded in the checklist notes rather than left as clarification markers. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…rompts Revise spec 029 so a display-language change takes effect immediately in the running session on every path (first-run screen and Settings), with no application restart and no "restart required" prompt, dialog or notice anywhere. This reverses spec 027's no-in-session-switching constraint. - First-run screen: confirm applies the language in place, then continues to the wizard (Release) or seeds sample data in the pre-wizard step and goes straight to the dashboard (Debug) - no restart on either path. - Settings: a saved language change re-renders the UI at once; the inline "restart required" notice and the planned post-save restart dialog are both gone. - Removed the standalone restart-capability requirements and all non-Windows self-restart degradation; added an in-session-switching requirement group. Requirements renumbered FR-001-FR-023, no gaps. - Updated success criteria, key entities, assumptions, edge cases, out-of-scope, and the spec-027 supersedes note; refreshed the requirements checklist notes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Phase 0/1 design artifacts for spec 029: research decisions (no-DB preference store, in-session CultureProvider.Switch, startup routing/ ladder changes, wizard cleanup, sample-data reuse, Settings restart- notice removal), data model, UI/service contract, and quickstart validation scenarios. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017sTJ4fm3PqXPQS4vLXzNHe
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017sTJ4fm3PqXPQS4vLXzNHe
37 tasks across Foundational + 3 user-story phases + Polish, generated from plan.md/research.md/data-model.md/contracts. Also records the tasks-step completion in .spec-context.json (status: ready-to-implement). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EyGsgcc7qvJL8vZLUZRttT
…+ US1 tests Phase 2 (Foundational) + partial Phase 3 (US1) of spec 029: - ILanguagePreferenceStore (Core contract) + MauiLanguagePreferenceStore (Preferences-backed impl), registered in MauiProgram - CultureProvider.Switch(CultureInfo) — in-session live culture change, no restart, re-renders the whole ShellLayout-wrapped tree - LanguageProvider's FR-006 startup ladder gains step 2 (recorded preference), renumbering OS-language/en-AU fallback to 3/4 - New FirstRunLanguageScreen (@page "/language-select"): lists shipped languages, pre-selects the resolved default, confirm records the preference + switches culture + navigates to /setup - App.razor.cs routes to /language-select vs /setup based on whether a preference is already recorded (only while setup is incomplete) - Tests: CultureProviderTests, FirstRunLanguageScreenTests, AppRoutingTests (new); LanguageProviderTests extended; SetupWizardTests, ReviewTabTests, LanguagePickerRenderTests updated to drop the sample-data/language-tab assertions being retired; SetupWizardNoSeederTests and SampleDataTabTests deleted outright (their premise stops existing) Full solution build is green. SetupWizard/LanguageSelectionTab/SampleDataTab/ ReviewTab themselves are not yet edited (tasks.md T018-T023) — several of the above tests (e.g. Tabs_AreNeverDisabled, SeedDataCheckbox_IsAbsent) will run red until that follow-up lands, since the wizard still carries the language step, sample-data checkbox and tab-bypass mechanism this story retires. Tasks T001-T017 of specs/029-first-run-language-seed/tasks.md complete. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017iVwWMUFaY9D9L83qmVWbb
…ption Finishes Phase 3 (US1) of spec 029, tasks T018-T023: - Delete LanguageSelectionTab and SampleDataTab outright (spec 029 FR-016/ FR-017: no language selector, no sample-data option, no variation in the wizard's step list — both retired to the new /language-select screen) - SetupWizard.razor: drop the two tabs' markup and every Disabled="@_seedWithTestData" attribute — tabs are now always enabled; drop the now-dead seeding overlay (the wizard never seeds any more, moved to FirstRunLanguageScreen) - SetupWizard.razor.cs: remove _seedWithTestData/_debugSeeder/IsTabBypassed/ HandleSeedWithTestDataChanged and the IDebugDataSeeder resolution; Next advances one tab at a time (no bypass skip); the opening-balance Finish guard is unconditional; LanguageCode now comes from the cascaded CultureProvider.CurrentCulture, not a wizard field - Drop the now-unwritten LanguageCode field from SetupFormModel - ReviewTab: drop DebugSeederAvailable/SeedWithTestData params and the "Load sample data" summary row; drop the orphaned Setup_Review_LoadSampleDataTerm resx key (all 4 culture files) Test-infrastructure fix (found via full-suite verification, not part of tasks.md but required for a green build): - CultureProviderTests.cs had un-awaited cut.InvokeAsync(...) calls, so CultureProvider.Switch's static CultureInfo mutation could still be in-flight when the test's restore ran — this alone caused ~116 failures scattered across totally unrelated tests (English-text assertions silently seeing French). Fixed by awaiting every InvokeAsync call. - FirstRunLanguageScreenTests.cs's three confirm-button tests trigger the same Switch call via a synchronous .Click() but had no culture restoration at all — fixed with the same shared CultureRestorer helper (extracted to tests/StageFright.UI.Tests/CultureRestorer.cs). - Even with correct per-test restoration, Switch's mutation of the process-wide CultureInfo.Default* statics is observable by any other test collection running concurrently — reproduced three times across separate full runs, each time corrupting a different unrelated test. Added AssemblyCollectionBehavior.cs (DisableTestParallelization) to StageFright.UI.Tests: the standard xUnit fix when many tests share global mutable state that virtually the whole suite reads (unlike the narrower per-class MoneyFormatterStateCollection precedent in StageFright.Integration.Tests, tagging every consumer here would mean tagging nearly the entire ~600-test project). Verified: 629/629 pass reliably across repeated full runs with this in place (was previously 629/629, 628/629, or 625/629 depending on scheduling luck). Full solution: dotnet build 0 warnings/0 errors; dotnet test 2062/2062 passed across all six projects (Core.Tests 751, Reports.Tests 236, Localization.Tests 28, Data.Tests 158, UI.Tests 629, Integration.Tests 260). User Story 1 (P1/MVP) is now independently complete and testable per tasks.md's Phase 3 checkpoint: a clean install shows /language-select before the wizard; confirming a language applies it immediately with no restart; the wizard never shows a language selector or sample-data option and its step list never varies; a recorded preference is never re-prompted. Tasks T018-T023 of specs/029-first-run-language-seed/tasks.md complete. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017iVwWMUFaY9D9L83qmVWbb
The previous write-context.py --step-summary call omitted --step and defaulted to "specify"; moved the summary to "implement" where it belongs. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017iVwWMUFaY9D9L83qmVWbb
…e dropped Completes Phase 4 (US2) of spec 029, tasks T024-T028: - GeneralSettingsTab.razor.cs: drop the LanguageChanged property and _initialLanguageCode field entirely — the "was this changed" check is now a one-off local comparison against _settings.LanguageCode captured right before HandleSaveAsync overwrites it, so no dedicated field is needed. After a successful SettingsService.SaveAsync where the language changed, call ILanguagePreferenceStore.Set(_selectedLanguageCode) then CultureProvider.Switch(...) — the exact same record-then-switch sequence FirstRunLanguageScreen uses, via a newly-cascaded CultureProvider - GeneralSettingsTab.razor: remove the "restart the app" alert block — there's nothing to wait for now, the change is visible the moment it's saved (FR-010/FR-020/SC-007) - Drop the now-orphaned Settings_General_LanguageRestartNotice resx key (all 4 culture files) - Tests: GeneralSettingsTabTests gains cases for changed-language save (records preference + switches culture) and unchanged-language save (does neither), plus an explicit "no restart notice ever, before/after change/save" assertion; LanguagePickerRenderTests drops the now-false "shows restart notice" case and broadens the "no notice" case to also check after a change Full solution: dotnet build 0 warnings/0 errors; dotnet test 2064/2064 passed across all six projects (631 in UI.Tests, +2 net from these tests). User Story 2 (P2) is now independently complete per tasks.md's Phase 4 checkpoint: changing the language in Settings and saving re-renders the whole app in the new language in the same interaction, no restart notice ever shown, and the choice survives a relaunch (via the already-existing Settings.LanguageCode persistence). User Stories 1 and 2 both work independently. Tasks T024-T028 of specs/029-first-run-language-seed/tasks.md complete. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017iVwWMUFaY9D9L83qmVWbb
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017iVwWMUFaY9D9L83qmVWbb
Phase 5 (T029-T032): FirstRunLanguageScreen now optionally resolves IDebugDataSeeder (Debug builds only) and offers a "Load sample data" RadzenSwitch. Confirming with it ticked builds a placeholder SetupRequest, calls SetupService.InitializeAsync then IDebugDataSeeder.SeedAsync under a seeding-progress overlay (same try/finally shape the deleted SetupWizard used), and lands on /dashboard with the wizard never shown. A seeding failure shows Setup_FirstRun_SeedingError and stays on the screen. Added Setup_FirstRun_SeedingError and Setup_FirstRun_SampleOrganizationPlaceholder resx keys (neutral + en-US/fr-FR/qps-ploc) — the placeholder org name is routed through a key rather than a literal because Us2LocalizationGuardTests bans bare string literals on this scanned UI surface, even for a value DebugDataSeeder immediately overwrites. Also fixes a genuine bUnit test-timing bug found while verifying this task: a synchronous Click() on the confirm button doesn't reliably await the real Task.Run seeding hop, so the two new tests use ClickAsync(new MouseEventArgs()) instead, matching the codebase's existing convention for async-submit buttons elsewhere. Full solution build: 0 warnings/0 errors. Full test suite: 2067/2067 passed across all six projects. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017iVwWMUFaY9D9L83qmVWbb
…erification finds in-session-switch defect T033: CLAUDE.md Navigation + Localization sections updated for the /language-select screen and the live CultureProvider.Switch (no restart notice; no next-launch delay). T034: docs/localization/adding-a-language.md — language-picker references moved from "Setup Wizard language step" to /language-select (one-minute version, SetupResource table row, step 5, qps-ploc note); section 9 retitled and rewritten with the 4-tier startup ladder (adds the ILanguagePreferenceStore no-DB preference tier) and the live in-session switch for both entry points. T035: dotnet build -c Debug 0 warnings/0 errors; full test suite 2067 pass (the sole failure, EventFormTests.DoesNotRender_FeeOrPaidFields, is the CLAUDE.md-documented GUID-substring flake — green in isolation); quickstart scenarios 1–5 filters all green. T036: live CDP-driven verification on a clean install. PASS — /language-select renders before /setup; lists en-AU/en-US/fr-FR by endonym; en-AU pre-selected; choice persists to preferences.dat outside the DB; no process restart on confirm (PID stable); a relaunch with the preference recorded resolves to fr-FR (log) and opens /setup fully in French, skipping /language-select. FAIL — confirming fr-FR on /language-select does NOT re-render the running session: /setup stayed entirely English. CultureProvider.Switch has no visible effect in the real MAUI WebView2 host; the new language only appears after a full app restart (the issue #361 symptom, on the first-run path). Recorded as a blocking concern in .spec-context.json; suspected AsyncLocal CurrentUICulture pinned at startup. Needs its own debug+fix+test pass (US1 T004/T016, US2 T027). T037 not run (per user) — exercises the same CultureProvider.Switch mechanism (FR-009), blocked on the same defect. Spec left at status=implementing — NOT marked complete. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
T037 (Phase 6, deferred-until-implementation live verification) is now confirmed by the user: in the real MAUI app, changing the Display language in Settings → General and saving re-renders the running session in the new language immediately, with no process restart and no "restart required" notice at any point (before the change, after the selection changes, or after the save). Verified in both light and dark themes. This is the Settings entry point (FR-009). Contrary to the T036 note's presumption that it would be "broken identically", the in-session CultureProvider.Switch re-render IS effective here in the real WebView2 host — Settings renders inside the live cascaded CultureProvider, so StateHasChanged propagates to its descendants. The T036 blocking concern is a different code path (first-run /language-select confirm → navigate to /setup, which does not re-render live in the real app) and is unaffected — it remains OPEN and still needs its own debug+fix+test pass. Spec left at status=implementing, NOT marked complete. - tasks.md: T037 checked, with the manual-verification note inline - .spec-context.json: T037 history + task_summary + verified entry; currentTask → T037; last_action refreshed - .spec-context.events.jsonl: T037 completion event appended No code changed — docs/companion-state only, so no build/test run (matches the prior companion-state-only commit 6416ba0). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ture globals only (T036) The T036 live check found the spec-029 in-session switch had no effect in the real WebView2 host: confirming a language on /language-select persisted it and navigated on, but /setup rendered in the OLD language; only a full process restart applied it (the issue #361 symptom). Root cause (confirmed by a deterministic ExecutionContext.Run repro): CultureInfo.CurrentCulture / CurrentUICulture are AsyncLocal-backed. MauiProgram.RunStartupSequence pinned them on the startup thread; that per-execution-context value *shadows* the DefaultThreadCurrent* globals on the Blazor renderer's own ExecutionContext. CultureProvider.Switch updated the globals (and set CurrentUICulture on the unwinding click-handler context), but every queued render kept resolving the stale pinned value. A restart worked only because startup then re-pinned the new culture. bUnit / integration tests passed because they render synchronously on the same context that calls Switch. Fix — set ONLY the process-wide statics, never the per-context override: - MauiProgram.RunStartupSequence: assign CultureInfo.DefaultThreadCurrentCulture / DefaultThreadCurrentUICulture; drop the CurrentCulture / CurrentUICulture assignments. - CultureProvider.Switch: same — assign the two DefaultThreadCurrent* globals, update the cascaded CurrentCulture property, StateHasChanged(). No per-context pin, so a second switch can't re-introduce the shadow either. With no override anywhere, CultureInfo.CurrentUICulture reads straight through to DefaultThreadCurrentUICulture on every render, so the switch is visible on the very next render regardless of which ExecutionContext runs it — the StateHasChanged() cascade re-render is unchanged. Tests: CultureProviderTests reworked — Switch_SetsTheProcessWideCultureGlobals asserts the real contract; new Switch_DoesNotPinAPerContextOverride_SoLater GlobalChangesStillTakeEffect is the regression guard (a later DefaultThread CurrentUICulture change is still observed — proof Switch pinned no override). Build 0 warnings / 0 errors; full suite 2068/2068 (UI.Tests 635, +1). Docs: CLAUDE.md Localization section; docs/localization/adding-a-language.md sec 9; spec 029 contract, data-model.md, and a dated correction to research.md Decision 2 (its "setting CurrentCulture on the calling thread reaches the next render" rationale was the flawed premise). Companion state: T036 concern marked resolved; verified entry added. Live in-app re-verification of the fix (first-run path, both themes, + a T037 re-confirm) is tracked as new task T038 — bUnit renders synchronously so it cannot reproduce the EC-boundary failure. Spec stays status=implementing until that live check passes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Localizer.FormatNamedPlaceholders: bind each distinct {token} to an arg
once and substitute every occurrence by name, so a repeated token renders
its value instead of leaking the literal "{token}" once args run out.
- Enum key format (Enum_<Type>_<Member>) now lives in one place —
EnumLocalizationExtensions.EnumResourceKey — used by both LocalizeEnum and
Localizer.Enum instead of being rebuilt in each.
- SetupService.Validate now range-checks FinancialYearStartMonth (1..12),
matching the existing day check; new Validation_Setup_FinancialYearStartMonthRange
key added to all four ValidationResource resx files. Contract doc updated.
- SupportedLanguagesCatalog: materialise the satellite-folder list eagerly so
an IO/permission failure during enumeration is caught, not thrown mid-foreach
out of BuildCatalog where Lazy<T> would cache it and break the catalog forever.
- FirstRunLanguageScreen sample-data path: guard InitializeAsync with
IsSetupCompleteAsync so re-pressing Confirm after a failed seed retries
seeding instead of dead-ending on "setup already completed". Spec 029 docs
updated.
- GeneralSettingsTab code-behind: resolve the save-success message through the
already-injected ILocalizer, consistent with its other lookups.
- CultureProvider: drop the redundant OnInitialized (field initialiser already
sets CurrentCulture to the same value).
- MoneyFormatter: document the process-wide-static test-isolation contract.
Review items #8 (RadzenSwitch <label for>) reviewed and dismissed — Radzen
renders a real labelable <input id="{Name}"> so the association is valid.
Tests: +7 (LocalizerTests placeholder cases, FY-start-month range, seed-retry
after failure). Full solution build clean (0 warnings); 2076 tests pass.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
029: first-run language selection & sample-data seeding (also carries specs 027 & 028)
Merge workflow changes
The `build-mac-app` job passed `-p:RuntimeIdentifier="maccatalyst-x64;maccatalyst-arm64"`.
Shell quoting stops the shell from splitting on `;`, but MSBuild's own `-p:` switch
parser still treats `;` as a property-list delimiter, so it read `maccatalyst-arm64`
as a second, malformed property and failed:
MSBUILD : error MSB1006: Property is not valid.
Switch: maccatalyst-arm64
Join the two RIDs with the MSBuild literal-semicolon escape `%3B` instead. MSBuild
un-escapes it to `;` during property parsing, yielding the single property value
`maccatalyst-x64;maccatalyst-arm64` that the xamarin-macios targets read as a
universal (lipo'd x64 + arm64) build.
Also updates the CLAUDE.md description of the workflow to match.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
fix(ci): escape RID separator in macOS debug pre-release publish (#366)
The macOS job in debug-pre-release.yml failed with: NETSDK1083: The specified RuntimeIdentifier 'maccatalyst-x64;maccatalyst-arm64' is not recognized It was passing the singular -p:RuntimeIdentifier with a semicolon list. The .NET SDK validates a lone RuntimeIdentifier as a single RID and rejects a list. MAUI's Mac Catalyst targets only rewrite a ;-list in the singular property into the plural RuntimeIdentifiers when it is set inside the .csproj (as MAUI does for Release builds) - not when it arrives as a command-line global property. A Debug CLI universal build must therefore pass the plural -p:RuntimeIdentifiers itself. The %3B escaping is unchanged and still required (issue #366). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ntifiers ci: use plural RuntimeIdentifiers for the Mac universal debug build
Passing -p:RuntimeIdentifiers=maccatalyst-x64%3Bmaccatalyst-arm64 on the command line made it a global property that propagated verbatim to every ProjectReference. The plain net10.0 class libraries (Core, Data, UI, Plugins.Contracts, Reports) then failed with NETSDK1083 on the joined ';'-list. Instead, add a conditional PropertyGroup to StageFright.App.csproj that sets the plural <RuntimeIdentifiers> only when -p:PublishMacUniversal=true is passed for a maccatalyst TFM, and have the workflow pass that flag. As a project property it is evaluated locally and MAUI's Mac Catalyst targets fan out per-RID inner builds that hand a single concrete RID to the referenced libraries. This mirrors dotnet/maui's own Controls.TestCases.HostApp.csproj CI pattern. The group is inert for every normal local/CI build, so single-arch Debug builds and `dotnet run` are unaffected. Verified: dotnet build src/StageFright.App (Debug, net10.0-windows) still succeeds with 0 warnings/errors - the new group stays dormant off-target. The universal maccatalyst build itself can only be exercised by re-running the workflow on a macOS runner. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Owner
Author
|
Incorrectly attempted to merge to maser. Should be merged to dev. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.