Global error notification and loading states (task 98) #71

Merged
rob merged 4 commits from feat/global-error-loading into main 2026-08-05 19:37:05 +00:00
Owner

Closes task 98. Also fixes task 139 in passing (see below).

ErrorNotification (src/PlaceMark.WebUI/Notifications/) is the one dismissible surface for an ApiException, replacing each page's own _generalError paragraph. Used by Login, Register, GroupForm and OidcComplete. role="alert" + aria-live="assertive", deliberately: every consumer shows this after a submit the visitor cannot proceed past. The dismiss control is a real <button> — keyboard operability is the native element's own guarantee, no custom keydown wiring. Falls back to "Something went wrong." if ever given an empty message, though nothing today reaches that path since ApiException.Message (ADR-0058) already builds a Detail-or-Title-or-status fallback.

Task 139 fixed here, not left open — same component area, same defect. #blazor-error-ui's dismiss <span> is now a <button>. Recommend closing 139.

Task 157 (FieldErrors' missing aria-describedby) is untouched — separate concern, needs its own id-generation design.

Loading states: submit buttons show their state in their label ("Signing in…" etc.), Login's SSO button now disables during its own async gap, App.razor gains an <Authorizing> template (role="status", polite) for the previously-blank gap while AuthorizeRouteView awaits the token store.

Reasoning and alternatives in ADR-0063.

Full CI sequence (build, test, dotnet format --verify-no-changes) run locally against the pinned SDK.

Closes task 98. Also fixes task 139 in passing (see below). **`ErrorNotification`** (`src/PlaceMark.WebUI/Notifications/`) is the one dismissible surface for an `ApiException`, replacing each page's own `_generalError` paragraph. Used by `Login`, `Register`, `GroupForm` and `OidcComplete`. `role="alert"` + `aria-live="assertive"`, deliberately: every consumer shows this after a submit the visitor cannot proceed past. The dismiss control is a real `<button>` — keyboard operability is the native element's own guarantee, no custom keydown wiring. Falls back to "Something went wrong." if ever given an empty message, though nothing today reaches that path since `ApiException.Message` (ADR-0058) already builds a Detail-or-Title-or-status fallback. **Task 139 fixed here, not left open** — same component area, same defect. `#blazor-error-ui`'s dismiss `<span>` is now a `<button>`. Recommend closing 139. **Task 157 (`FieldErrors`' missing `aria-describedby`) is untouched** — separate concern, needs its own `id`-generation design. **Loading states**: submit buttons show their state in their label ("Signing in…" etc.), `Login`'s SSO button now disables during its own async gap, `App.razor` gains an `<Authorizing>` template (`role="status"`, polite) for the previously-blank gap while `AuthorizeRouteView` awaits the token store. Reasoning and alternatives in [ADR-0063](docs/adr/0063-one-dismissible-error-notification-component.md). Full CI sequence (build, test, `dotnet format --verify-no-changes`) run locally against the pinned SDK.
Add a shared, dismissible ErrorNotification and fix the error banner's dismiss button
All checks were successful
CI / build (pull_request) Successful in 2m28s
14437377bf
rob left a comment

Verdict: mergeable

Build and full PlaceMark.WebUI.Tests suite (168 tests) pass under the pinned 10.0.100 SDK; dotnet format --verify-no-changes clean.

Accessibility checks all hold up:

  • ErrorNotification's dismiss control and index.html's #blazor-error-ui .dismiss are both genuine <button type="button"> elements with accessible names (visually-hidden text / aria-label). ErrorNotificationTests asserts TagName == "BUTTON" and that .Click() raises OnDismiss, not a CSS class toggle — correct given bUnit's non-browser DOM can't itself prove the Enter/Space-to-click browser mapping, and the tests say so rather than overclaiming.
  • BlazorErrorBannerTests reads index.html/app.css as text (nothing for bUnit to render) and states plainly what it can't prove: real click-handler wiring, which Blazor attaches lazily only on a genuine unhandled error. That gap is disclosed, not hidden.
  • role="alert" + explicit aria-live="assertive" is redundant (the role already implies it) but harmless — one live region, no double-announcement risk — and it's the right choice for a post-submit blocking error. Reasonable to state both since the ticket asks the choice be assertable directly rather than inferred.
  • <Authorizing>'s role="status" is correctly polite, fires once per pending navigation, not per keystroke — nothing upstream ties it to input.
  • Neither button's CSS strips outline, so keyboard focus visibility survives the chrome-stripping in both ErrorNotification.razor.css and app.css.

Reuse confirmed: Login, Register, GroupForm, OidcComplete all render ErrorNotification and none keeps a parallel _generalError paragraph. GroupForm's static .form-error ("Only the group's Owner can rename it.") is a distinct, non-dismissible, parameter-derived courtesy notice — not an API-driven error — so it's correctly left outside the component, and GroupFormTests now separately proves the server-side 403 refusal (stale CallerIsOwner) routes through ErrorNotification instead.

Empty-detail case verified: ApiProblemException's constructor falls back to problem.Detail ?? problem.Title ?? "The API answered {status} with no further detail." — never blank. ErrorNotification's own "Something went wrong." fallback is unreachable today but tested as defence-in-depth. No empty notification is possible.

The RenderTreeDiffBuilder NullReferenceException: reproduced it independently. However, my own isolation attempt shows the ADR's stated repro condition is imprecise. A bare CascadingAuthenticationState > Router > AuthorizeRouteView with no Authorizing template and no NotAuthorized override, resolving a pending auth state after render, does not throw — it completes cleanly. Adding RedirectToLogin as the NotAuthorized template (unchanged by this PR, pre-existing since task 86) is what reproduces the NRE: the trigger is a NotAuthorized-transition rendering a component that calls NavigateTo synchronously mid-diff, not merely the absence of an Authorizing template. The conclusion — pre-existing framework/bUnit hazard, unrelated to this PR's own markup — still holds (RedirectToLogin predates this PR and my repro used none of the new code), and the test's defensive strategy (don't resolve the pending task, prove only the loading state) is right regardless of the precise mechanism. But ADR-0063's phrase "a bare AuthorizeRouteView with no Authorizing template of any kind" overstates what's needed to trigger it and could mislead whoever next tries to close this gap. Worth a follow-up correction to the ADR text — not blocking.

Task 139 is genuinely closed, not partially: real <button>, accessible name, button chrome stripped, static shape covered by a new test — matches its acceptance criteria as far as anything without a browser can.

What bUnit cannot show (task 156, no browser here): actual Enter/Space keyboard activation of either dismiss button; the framework's lazy click-handler wiring on #blazor-error-ui firing from a genuine unhandled error; CSS hover/focus-visible rendering; and, as above, the <Authorizing>→resolved transition in a genuinely-awaited AuthorizeRouteView. All four are disclosed in code comments rather than silently assumed.

Verdict: mergeable Build and full `PlaceMark.WebUI.Tests` suite (168 tests) pass under the pinned 10.0.100 SDK; `dotnet format --verify-no-changes` clean. **Accessibility checks all hold up:** - `ErrorNotification`'s dismiss control and `index.html`'s `#blazor-error-ui .dismiss` are both genuine `<button type="button">` elements with accessible names (visually-hidden text / `aria-label`). `ErrorNotificationTests` asserts `TagName == "BUTTON"` and that `.Click()` raises `OnDismiss`, not a CSS class toggle — correct given bUnit's non-browser DOM can't itself prove the Enter/Space-to-click browser mapping, and the tests say so rather than overclaiming. - `BlazorErrorBannerTests` reads `index.html`/`app.css` as text (nothing for bUnit to render) and states plainly what it can't prove: real click-handler wiring, which Blazor attaches lazily only on a genuine unhandled error. That gap is disclosed, not hidden. - `role="alert"` + explicit `aria-live="assertive"` is redundant (the role already implies it) but harmless — one live region, no double-announcement risk — and it's the right choice for a post-submit blocking error. Reasonable to state both since the ticket asks the choice be assertable directly rather than inferred. - `<Authorizing>`'s `role="status"` is correctly polite, fires once per pending navigation, not per keystroke — nothing upstream ties it to input. - Neither button's CSS strips `outline`, so keyboard focus visibility survives the chrome-stripping in both `ErrorNotification.razor.css` and `app.css`. **Reuse confirmed**: `Login`, `Register`, `GroupForm`, `OidcComplete` all render `ErrorNotification` and none keeps a parallel `_generalError` paragraph. `GroupForm`'s static `.form-error` ("Only the group's Owner can rename it.") is a distinct, non-dismissible, parameter-derived courtesy notice — not an API-driven error — so it's correctly left outside the component, and `GroupFormTests` now separately proves the *server-side* 403 refusal (stale `CallerIsOwner`) routes through `ErrorNotification` instead. **Empty-detail case verified**: `ApiProblemException`'s constructor falls back to `problem.Detail ?? problem.Title ?? "The API answered {status} with no further detail."` — never blank. `ErrorNotification`'s own `"Something went wrong."` fallback is unreachable today but tested as defence-in-depth. No empty notification is possible. **The `RenderTreeDiffBuilder` NullReferenceException**: reproduced it independently. However, my own isolation attempt shows the ADR's stated repro condition is imprecise. A bare `CascadingAuthenticationState > Router > AuthorizeRouteView` with *no* `Authorizing` template and *no* `NotAuthorized` override, resolving a pending auth state after render, does **not** throw — it completes cleanly. Adding `RedirectToLogin` as the `NotAuthorized` template (unchanged by this PR, pre-existing since task 86) is what reproduces the NRE: the trigger is a `NotAuthorized`-transition rendering a component that calls `NavigateTo` synchronously mid-diff, not merely the absence of an `Authorizing` template. The conclusion — pre-existing framework/bUnit hazard, unrelated to this PR's own markup — still holds (`RedirectToLogin` predates this PR and my repro used none of the new code), and the test's defensive strategy (don't resolve the pending task, prove only the loading state) is right regardless of the precise mechanism. But ADR-0063's phrase "a bare `AuthorizeRouteView` with no `Authorizing` template of any kind" overstates what's needed to trigger it and could mislead whoever next tries to close this gap. Worth a follow-up correction to the ADR text — not blocking. Task 139 is genuinely closed, not partially: real `<button>`, accessible name, button chrome stripped, static shape covered by a new test — matches its acceptance criteria as far as anything without a browser can. **What bUnit cannot show** (task 156, no browser here): actual Enter/Space keyboard activation of either dismiss button; the framework's lazy click-handler wiring on `#blazor-error-ui` firing from a genuine unhandled error; CSS hover/focus-visible rendering; and, as above, the `<Authorizing>`→resolved transition in a genuinely-awaited `AuthorizeRouteView`. All four are disclosed in code comments rather than silently assumed.
Correct ADR-0063's RenderTreeDiffBuilder repro claim
All checks were successful
CI / build (pull_request) Successful in 2m22s
3c74064482
rob left a comment

Verdict: changes needed

Adjudicating the counter-claim at 3c74064, as requested. I do not accept the correction as it now stands — my own independent reproduction contradicts it, not just my original narrower claim.

What I ran. All four combinations of {<Authorizing> present/absent} × {resolves authorized/not authorized}, against the real App/MainLayout/Drawer stack for the "present" cases and a bare CascadingAuthenticationState > Router > AuthorizeRouteView (no Authorizing, no NotAuthorized override) for the "absent" cases, each dispatched two ways: direct TaskCompletionSource.SetResult from the test thread, and through bUnit's own IRenderedComponent<T>.InvokeAsync. After resolving, I didn't rely on WaitForState's race against Renderer.UnhandledException alone (that race can resolve either side first if both tasks are already complete when it's constructed) — I added a genuine 400ms wall-clock settle and then polled BunitContext.Renderer.UnhandledException directly, the exact field BunitRenderer.HandleException writes into, decompiled to confirm. I also captured final markup to confirm each case actually reached the state it claimed to (not silently diverted).

Result: only the original case throws. <Authorizing> present, resolves not authorized, NotAuthorized renders RedirectToLoginNullReferenceException in RenderTreeDiffBuilder, every time. Every other combination — authorized reaching Account's real content (confirmed via markup: <h1>Your account</h1>), and not-authorized with no RedirectToLogin (confirmed via markup: literal Not authorized, the framework's own default) — resolves cleanly, with Renderer.UnhandledException never completing, regardless of <Authorizing>'s presence or dispatch method. This holds for both new cases the correction commit says it tested (authorized outcome; InvokeAsync dispatch).

So on the specific, unhedged sentence — "All four combinations... were run and failed identically" — my reproduction disagrees, and it disagrees in the direction of my original narrower claim (RedirectToLogin's reentrant NavigateTo during the diff), not the revised broader one. I can't identify what differs between my setup and the author's from here — bunit resolved to 2.8.6 (matching the pinned version), build and full suite are clean at 3c74064. This is exactly the "one of you has a confounded experiment" case: two careful, controlled runs disagreeing on a testable claim, without either side's exact repro code in front of the other. I'd want to see the author's actual test code to diff against mine before anyone's account is treated as settled.

Judging the ADR text as written:

  • The broadened claim ("AuthorizeRouteViewCore completing its authorization check as a second render batch... independent of which template is missing or what the second batch renders") is stated as something "checked directly rather than taken on trust" and "run... and failed identically" — i.e. presented as observed fact, in this project's own "run, not reasoned" idiom (ADR-0022's phrase). My reproduction says that specific fact is wrong, not merely unproven.
  • The epistemic humility elsewhere is genuine and welcome — "the mechanism... is not nailed down," "closing it properly needs reading Blazor's or bUnit's own source." That framing is honest and I have no complaint with it in isolation.
  • It does correctly preserve the reasoning trail (keeps the superseded claim, explains why it was revised) — good ADR discipline on that front.
  • The problem is narrower than "dishonest": it's that a specific, falsifiable empirical sentence is asserted as settled fact between the honest hedges, and it doesn't survive a second independent check.

Ask: reconcile before merging — either share the exact repro (test code, not just prose) so the discrepancy can be pinned down and the ADR corrected a third time with something both sides can verify, or weaken the claim to what's actually agreed: that the trigger reproduces in at least the RedirectToLogin configuration, that a broader characterization was tried and is contested/unresolved, and that closing it needs source-level investigation rather than another plausible story from either side. Also worth fixing while this is open: AuthorizationTests.cs's own inline comment (around App_AuthenticationStateStillResolving_ShowsALoadingState) still states the original, now-superseded "bare AuthorizeRouteView with no Authorizing template of any kind" claim — the correction commit only touched the ADR, so the test file and the ADR now disagree with each other too.

The accessibility work from the first review (real <button>s, role/aria-live choices, no duplicate error surfaces, empty-detail fallback, task 139 closure) is unaffected by any of this and still stands.

Verdict: changes needed Adjudicating the counter-claim at `3c74064`, as requested. I do **not** accept the correction as it now stands — my own independent reproduction contradicts it, not just my original narrower claim. **What I ran.** All four combinations of {`<Authorizing>` present/absent} × {resolves authorized/not authorized}, against the real `App`/`MainLayout`/`Drawer` stack for the "present" cases and a bare `CascadingAuthenticationState > Router > AuthorizeRouteView` (no `Authorizing`, no `NotAuthorized` override) for the "absent" cases, each dispatched two ways: direct `TaskCompletionSource.SetResult` from the test thread, and through bUnit's own `IRenderedComponent<T>.InvokeAsync`. After resolving, I didn't rely on `WaitForState`'s race against `Renderer.UnhandledException` alone (that race can resolve either side first if both tasks are already complete when it's constructed) — I added a genuine 400ms wall-clock settle and then **polled `BunitContext.Renderer.UnhandledException` directly**, the exact field `BunitRenderer.HandleException` writes into, decompiled to confirm. I also captured final markup to confirm each case actually reached the state it claimed to (not silently diverted). **Result: only the original case throws.** `<Authorizing>` present, resolves *not* authorized, `NotAuthorized` renders `RedirectToLogin` → `NullReferenceException` in `RenderTreeDiffBuilder`, every time. Every other combination — authorized reaching `Account`'s real content (confirmed via markup: `<h1>Your account</h1>`), and not-authorized with no `RedirectToLogin` (confirmed via markup: literal `Not authorized`, the framework's own default) — resolves **cleanly**, with `Renderer.UnhandledException` never completing, regardless of `<Authorizing>`'s presence or dispatch method. This holds for both new cases the correction commit says it tested (authorized outcome; `InvokeAsync` dispatch). So on the specific, unhedged sentence — "All four combinations... were run and failed identically" — my reproduction disagrees, and it disagrees in the direction of my *original* narrower claim (`RedirectToLogin`'s reentrant `NavigateTo` during the diff), not the revised broader one. I can't identify what differs between my setup and the author's from here — `bunit` resolved to 2.8.6 (matching the pinned version), build and full suite are clean at `3c74064`. This is exactly the "one of you has a confounded experiment" case: two careful, controlled runs disagreeing on a testable claim, without either side's exact repro code in front of the other. I'd want to see the author's actual test code to diff against mine before anyone's account is treated as settled. **Judging the ADR text as written:** - The broadened claim ("`AuthorizeRouteViewCore` completing its authorization check as a second render batch... independent of which template is missing or what the second batch renders") is stated as something "checked directly rather than taken on trust" and "run... and failed identically" — i.e. presented as observed fact, in this project's own "run, not reasoned" idiom (ADR-0022's phrase). My reproduction says that specific fact is wrong, not merely unproven. - The epistemic humility elsewhere is genuine and welcome — "the mechanism... is not nailed down," "closing it properly needs reading Blazor's or bUnit's own source." That framing is honest and I have no complaint with it in isolation. - It does correctly preserve the reasoning trail (keeps the superseded claim, explains why it was revised) — good ADR discipline on that front. - The problem is narrower than "dishonest": it's that a specific, falsifiable empirical sentence is asserted as settled fact between the honest hedges, and it doesn't survive a second independent check. **Ask**: reconcile before merging — either share the exact repro (test code, not just prose) so the discrepancy can be pinned down and the ADR corrected a third time with something both sides can verify, or weaken the claim to what's actually agreed: that the trigger reproduces in at least the `RedirectToLogin` configuration, that a broader characterization was tried and is contested/unresolved, and that closing it needs source-level investigation rather than another plausible story from either side. Also worth fixing while this is open: `AuthorizationTests.cs`'s own inline comment (around `App_AuthenticationStateStillResolving_ShowsALoadingState`) still states the *original*, now-superseded "bare `AuthorizeRouteView` with no `Authorizing` template of any kind" claim — the correction commit only touched the ADR, so the test file and the ADR now disagree with each other too. The accessibility work from the first review (real `<button>`s, `role`/`aria-live` choices, no duplicate error surfaces, empty-detail fallback, task 139 closure) is unaffected by any of this and still stands.
Commit the ADR-0063 repro as skipped tests, correct the record
All checks were successful
CI / build (pull_request) Successful in 2m7s
f8c2b8073d
rob left a comment

Verdict: mergeable

Re-reviewed f8c2b80 as a real pass, not a lap of honour — independently re-ran the claims rather than reading them and agreeing.

AuthorizeRouteViewDiffingReproTests.cs verified by execution, not inspection. I temporarily removed the Skip and ran all four: TemplateAbsent_ResolvesAuthorized and TemplateAbsent_ResolvesAnonymous pass clean, TemplatePresent_ResolvesAuthorized passes clean, TemplatePresent_ResolvesAnonymous throws the identical NullReferenceException in RenderTreeDiffBuilder. Matches the file's own claims exactly, on my toolchain independently of the author's.

I then specifically checked the file's most load-bearing claim — that the earlier "authorized path throws" result was a missing-DI false positive — by deliberately removing the PlaceMarkAuthenticationStateProvider registration from ConfigureAuthorization() and re-running TemplatePresent_ResolvesAuthorized: it throws the same NullReferenceException, not a clean InvalidOperationException, at the same point in the stack. That confirms the explanation itself, not just the corrected conclusion. Reverted cleanly afterward (git checkout -- matches the committed file byte for byte).

All four tests share ConfigureAuthorization(), so the registration is present in all four, not just the one that used to be missing it — confirmed by reading and by the fact that none of the other three regressed to throwing for an unrelated DI reason.

Full suite passes with the four repro tests genuinely skipped (168 passed, 4 skipped), dotnet format --verify-no-changes clean.

One documentation inaccuracy, non-blocking. BareAuthorizeRouteViewWithoutAuthorizingTemplate's doc comment claims it's "the same shape App.razor had before task 98 added an <Authorizing> template." Checked against the pre-PR App.razor (merge-base 127e787): that shape had a real Router and FocusOnNavigate around the AuthorizeRouteView; this harness has neither, using a hand-built static RouteData instead. It's a further reduction, not the same shape — worth a wording fix (e.g. "a further-reduced version of App.razor's pre-task-98 shape — no Router, no FocusOnNavigate either") so a future reader doesn't treat TemplateAbsent_ResolvesAnonymous as isolating only the <Authorizing> variable when it also drops Router. Doesn't affect the class's actual conclusion, which is honest about testing "the full tree" as one bundled variable rather than claiming to have isolated Router specifically.

ADR-0063 correctly separates settled from open. "Which combination reproduces" is now stated as agreed across two independent reproductions (confirmed a third time by me, above) rather than asserted unilaterally; "the mechanism" is explicitly left open with a plausible confound named (Router's own reaction to the reentrant NavigateTo, per the note above) rather than re-asserted as fact. This is the right shape — round three doesn't overclaim in the opposite direction, and it keeps the dispute trail rather than smoothing it away.

AuthorizationTests.cs's comment now matches: scoped to the specific RedirectToLogin-rendering case, points to the ADR and the repro class, no longer restates the superseded "any outcome" claim.

Accessibility work confirmed untouched: no diff in Notifications/, wwwroot/index.html, wwwroot/css/, Pages/Auth/, or Groups/ since the original 1443737. Nothing from the first review needs re-checking.

Verdict: mergeable Re-reviewed `f8c2b80` as a real pass, not a lap of honour — independently re-ran the claims rather than reading them and agreeing. **`AuthorizeRouteViewDiffingReproTests.cs` verified by execution, not inspection.** I temporarily removed the `Skip` and ran all four: `TemplateAbsent_ResolvesAuthorized` and `TemplateAbsent_ResolvesAnonymous` pass clean, `TemplatePresent_ResolvesAuthorized` passes clean, `TemplatePresent_ResolvesAnonymous` throws the identical `NullReferenceException` in `RenderTreeDiffBuilder`. Matches the file's own claims exactly, on my toolchain independently of the author's. I then specifically checked the file's most load-bearing claim — that the earlier "authorized path throws" result was a missing-DI false positive — by deliberately removing the `PlaceMarkAuthenticationStateProvider` registration from `ConfigureAuthorization()` and re-running `TemplatePresent_ResolvesAuthorized`: it throws the **same** `NullReferenceException`, not a clean `InvalidOperationException`, at the same point in the stack. That confirms the explanation itself, not just the corrected conclusion. Reverted cleanly afterward (`git checkout --` matches the committed file byte for byte). All four tests share `ConfigureAuthorization()`, so the registration is present in all four, not just the one that used to be missing it — confirmed by reading and by the fact that none of the other three regressed to throwing for an unrelated DI reason. Full suite passes with the four repro tests genuinely skipped (168 passed, 4 skipped), `dotnet format --verify-no-changes` clean. **One documentation inaccuracy, non-blocking.** `BareAuthorizeRouteViewWithoutAuthorizingTemplate`'s doc comment claims it's "the same shape `App.razor` had before task 98 added an `<Authorizing>` template." Checked against the pre-PR `App.razor` (merge-base `127e787`): that shape had a real `Router` and `FocusOnNavigate` around the `AuthorizeRouteView`; this harness has neither, using a hand-built static `RouteData` instead. It's a further reduction, not the same shape — worth a wording fix (e.g. "a further-reduced version of `App.razor`'s pre-task-98 shape — no `Router`, no `FocusOnNavigate` either") so a future reader doesn't treat `TemplateAbsent_ResolvesAnonymous` as isolating only the `<Authorizing>` variable when it also drops `Router`. Doesn't affect the class's actual conclusion, which is honest about testing "the full tree" as one bundled variable rather than claiming to have isolated `Router` specifically. **ADR-0063 correctly separates settled from open.** "Which combination reproduces" is now stated as agreed across two independent reproductions (confirmed a third time by me, above) rather than asserted unilaterally; "the mechanism" is explicitly left open with a plausible confound named (`Router`'s own reaction to the reentrant `NavigateTo`, per the note above) rather than re-asserted as fact. This is the right shape — round three doesn't overclaim in the opposite direction, and it keeps the dispute trail rather than smoothing it away. **`AuthorizationTests.cs`'s comment** now matches: scoped to the specific `RedirectToLogin`-rendering case, points to the ADR and the repro class, no longer restates the superseded "any outcome" claim. **Accessibility work confirmed untouched**: no diff in `Notifications/`, `wwwroot/index.html`, `wwwroot/css/`, `Pages/Auth/`, or `Groups/` since the original `1443737`. Nothing from the first review needs re-checking.
Merge remote-tracking branch 'origin/main' into feat/global-error-loading
All checks were successful
CI / build (pull_request) Successful in 2m36s
71f6a1d59b
# Conflicts:
#	docs/adr/README.md
#	src/PlaceMark.WebUI/_Imports.razor
rob merged commit 11b3800ad1 into main 2026-08-05 19:37:05 +00:00
rob deleted branch feat/global-error-loading 2026-08-05 19:37:05 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rob/PlaceMark!71
No description provided.