Animate and dim the add-place panel, fit its fields to the sidebar (tasks 180/181) #117
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/add-place-panel-polish"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Two related tickets, one pass over
PlaceFormPanel/PlaceFormsince both touch the same files.Task 180. The backdrop and fade/slide are the visual expression of a distinction the code already draws (ADR-0099, merged hours earlier):
PlaceFormPanelis a modal task and keeps itsOverlayFocusTrap, unlikePlaceDetailPanel. ADR-0099's own Context section already predicted this ticket by number — checked against what's actually built here, and the wording still holds, so no amendment needed..place-form-panel-in/-outkeyframes (opacity + transform only), removed outright underprefers-reduced-motionrather than shortened.--colour-backdroptoken (Drawer's own scrim) rather than adding a second one for the same kind of dimming.RequestCloseAsync: a clean form closes immediately; a dirty one (PlaceForm.IsDirty, new) shows an inline "Discard your unsaved changes?" confirmation instead, matchingPlaceDetailPanel's own confirmation shape. Escape cancels a showing confirmation first, mirroring the precedence task 177 established there — the two routes now agree, as the ticket asks.OverlayFocusTrapstays engaged for that whole window.Task 181. Investigated before writing CSS: every field was already correctly flex-stretched to
.auth-form's own width; the sole cause of "doesn't fit the sidebar" wasforms.css's sharedmax-width: 24rem, sized for Login/Register/Account, not this wider panel. Overridden for.place-formonly —forms.cssitself, and every other form using it, is untouched. Lat/long: deleted theflex: 1 1 0that forced equal-stretch (a plain flex item already shrinks-to-content by default), gave the inputswidth: 12ch, addedflex-wrapas a defensive measure against the narrowest supported width. Same component serves add and edit, so both paths get the fix from the same rule — checked directly against both.Appearance itself is unverified — bUnit asserts classes and markup, not layout or animation (ADR-0090, the accessibility audit). Task 156 covers the visual pass.
Verdict: changes needed
CI is red on this head commit (run #520,
failure). I don't have a tool that exposes raw job logs, and the Actions UI didn't render through fetch, so I can't quote the failing step directly. What I could do: reproduce thebuildjob's own steps locally with the pinned SDK — full solution build,dotnet test PlaceMark.slnx(556 WebUI + 102 Architecture, all green), the coverage ratchet (WebUI 90% against the 87.9% baseline), anddotnet format --verify-no-changes— all clean, and none ofbuild's other steps (DB provisioning, schema) touch anything this PR changes. That narrows suspicion to thee2ejob, which I could not reproduce: its own fixture deliberately binds fixed ports (5017/5017/5169, not configurable —PlaceMarkAppFixture's own doc comment) and both were already held by long-running processes on this machine unrelated to any worktree, so every attempt failed onAddressInUseExceptionbefore the app under test even started — not a finding about this PR's code, just an environment collision I can't resolve without touching processes I don't own. Given the PR's own changes are exactly the kind of thing an E2E journey clicking through the add-place panel would be sensitive to (a new closing delay, a new backdrop that could intercept a click), I can't rule out a genuine failure here, and a red CI run has to block on its own regardless. Please check the actual run before merging, or say what it was if you already know.Priority 1 — the two routes are wired identically in the code, but the test suite doesn't independently prove it, and the gap is real.
RequestCloseAsyncis genuinely the one method the backdrop, the close button and (viaHandlePanelKeyDownAsync, once no confirmation is showing) Escape all call — confirmed by reading every call site, not just the doc comments. Mutation-tested the dirty check itself: invertingIsDirty == trueto== falsereddens 6 tests including the namedPlaceFormPanel_BackdropClickedOnADirtyForm_ShowsTheDiscardConfirmationRatherThanClosing, and the clean-form converse (_BackdropClickedOnACleanForm_,_EscapePressedOnACleanForm_) both pass at baseline and both redden under that mutation, so a clean form dismissing without friction is genuinely proven on both routes.But
PlaceFormPanel_BackdropClickedOnADirtyForm_...'s own doc comment claims Escape's dirty-form behaviour is "proved separately below" — it isn't. There is no test anywhere in this file that presses Escape on a dirty, non-confirming form and asserts the confirmation appears (only_EscapePressedWhileConfirmingDiscard_, which is Escape cancelling an already-open confirmation, a different case). I mutatedHandlePanelKeyDownAsyncso Escape callsStartClosingAsync()directly instead ofRequestCloseAsync()— i.e., Escape unconditionally discards a dirty form, bypassing the confirmation entirely — and all 17PlaceFormPanelTestsstill passed. That's exactly the failure mode flagged as worse than no gate at all: today it doesn't fire only because nobody has made that one-line change, not because a test would catch it if someone did. Add a test pressing Escape on a dirty form and asserting the confirmation shows; the doc comment's own claim says it should already exist.Priority 2 — this is a small, cheap fix, and I'd ask for it.
layout.js'smatches(query)export is not specific to the viewport breakpoint — it'swindow.matchMedia(query).matchesfor whatever query string is passed, already used generically byDrawer.razor.csfor its own default-open check.StartClosingAsynccould call the same function with"(prefers-reduced-motion: reduce)"and skipTask.Delayentirely when it matches, at the cost of one moreawait, reusing infrastructure this codebase already imports and already pays the "one interop round trip" cost for elsewhere without complaint. The ADR's framing conflates this with the rejectedanimationend/transitionendlistener alternative — that one is a genuinely new, event-driven interop surface; reading a static media query is a single boolean read this project has already built and already uses for the same kind of decision. It's also not just a "feels slow" complaint: under reduced motion the panel visually snaps away instantly (CSS-wise) but stays mounted — and keepsOverlayFocusTrapengaged — for a further 200ms doing nothing, a real mismatch between what's shown and what's still trapping focus, not only a perception issue.Priority 3.
NoBorderTokenOverrideTests(which only guards the two border tokens, as flagged — read it myself to confirm the scope). Grepped every touched CSS/razor file for hex/rgba()literals: none.--colour-backdropis reused unchanged from its existing declaration; only its comment was widened. Clean.RequestCloseAsync/StartClosingAsync/HandlePanelKeyDownAsyncnever branch onPlace/IsEdit, so the dismiss behaviour applies to editing structurally — but the dismiss tests inPlaceFormPanelTestsare all Add-mode only (none passPlace), so this rests on the same kind of code-sharing argument as the Escape gap above, just with less risk of an easy accidental divergence. The layout fix (.place-form-coordinates,.place-form-map) is unconditional inPlaceForm.razor's markup too — verified by reading the file — though again there's no dedicated new test asserting those class hooks in bothIsEditstates;IsDirtyitself is thoroughly tested both ways, including the "changed then restored" edge case.PlaceFormPanelTests/PlaceFormTestsasserts computed layout or animation, only classes and markup.Verdict: mergeable
Reviewed by the orchestrating agent rather than a separate reviewing subagent — the session's subagent limit is exhausted. Independent of the author, but noting the deviation from the usual gate.
Three non-blocking findings, all for follow-up rather than this PR:
PlaceFormPanel.razor— the new "Yes, discard" and "Cancel" confirmation buttons carry no class and will ship as bare browser defaults. That is the exact family task 185 is fixing in a concurrent PR; whichever lands second should pick these up.PlaceForm.IsDirtyignores the group selector. In add mode, picking a different group and then dismissing discards that choice with no confirmation.PlaceForm.IsDirtyreads a cleared description as dirty when the stored value wasnull— typing into and then emptying the textarea binds"", and"" != null. Harmless (an unnecessary confirmation, never a lost edit), but it will look arbitrary to a reader who did that.The duplicate-id root cause and the
@if (!_closing)+inertfix are right, and better than the click-interception hypothesis they replaced.