Conversation
…p the dialog open
… the right dialog button
This was referenced Sep 24, 2026
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.
What changed
Security, crash and data-loss fixes for the operator dashboard:
Visible in: admin.
Before you start
admin.civfix.dev, once Admin test harness and characterization safety net #38 and this PR have merged to main.<img src=x onerror=alert(1)>, a cleanup event, an Inbox email with an attachment, an Active user and a Banned user.Verify
[Admin]
Map tooltips:
<img src=x onerror=alert(1)>. Expect: the tooltip shows that text literally, followed by·and the place. No alert appears and no broken image renders.Malformed links:
2. Open
admin.civfix.dev/#/reports/%E0%A4%Ain a new tab. Expect: the "Dashboard" loads. Before this fix the page stayed blank.Error boundaries:
3. From the "Dashboard", open DevTools, switch the network to Offline, and click a tile you have not opened yet, for example "Analytics". Expect: the top bar stays, and the page shows "This page could not load" and "The dashboard may have been updated. Reload to get the latest version." with a single "Reload" button.
4. Click "Dashboard" in the top bar. Expect: the Dashboard renders normally. Switch back online and click "Reload" to recover the section.
Confirm dialogs:
5. Open "Users", pick an Active account and click "Ban account". Expect: the "Ban user" dialog opens with focus on "Cancel".
6. Press Enter. Expect: the dialog closes, nobody is banned, and focus returns to "Ban account".
7. Reopen it, click the dialog's body text, and press Enter. Expect: nothing happens and the dialog stays open.
8. Press Tab once to reach "Ban", then press Enter. Expect: the account is banned and a toast confirms it. Undo with "Un-ban" (Tab to "Un-ban", then Enter).
9. Open "Organizations", select an org and click "Suspend". Type a reason, then press inside the reason box, drag out past the dialog edge and release over the dark backdrop. Expect: the dialog stays open with the reason intact.
Draft protection:
10. Open "Mail" and click "Compose". Type a subject, then press Escape and click the backdrop. Expect: "New message" stays open with the subject, and the page does not jump to the Dashboard.
11. Click "Cancel". Expect: the modal closes and focus returns to "Compose".
12. In "Mail", click "Default template" and edit the body. Press Escape, then click the backdrop. Expect: the edit is kept. "Cancel" discards it.
13. In "Jurisdictions", pick a row and click "Edit template". Expect: it behaves the same as step 12.
14. In "Events", select a cleanup and click "Link reports". Select one report, then press Escape and click the backdrop. Expect: the modal stays open with the selection. With only search text typed and nothing selected, Escape closes it.
15. In "Organizations", click "New organization", type a name, then click the dimmed backdrop. Expect: the panel stays open with the name. "Cancel" discards it.
Attachments:
16. In "Mail", switch to "Inbox" and open an email with an attachment. Expect: the attachment appears as a chip with the file name and size, and opens in a new tab.
Concurrent profile edits:
17. Operator A: "Organizations" → select an org → "Edit profile" → change only the description. Operator B: edit the same org's website and save. Operator A: wait 30 seconds, switch the network offline and back online, then "Save changes" with a reason. Expect: the website still shows B's value and the description shows A's.
Regression
Other confirm and reason dialogs: [Admin]
Photos: [Admin]
Escape and error states: [Admin]
Status pills: [Admin]
Not covered
Findings addressed
bindTooltipreceived citizen text as an HTML string. It now gets a text node. The divIcon glyph lookup uses own keys only. A full sink audit found no other HTML sinks: attribution strings are constants, and divIcon interpolates constants only.decodeURIComponenton the hash threw at module load and on popstate. The route now falls back to home.React.lazycaches the failed import.role="dialog",aria-modaland a label;hrefs are now allowed only for http(s) URLs, withrel="noopener noreferrer". One chip component shows "link unavailable" otherwise. The backend already presigns these keys, so real links keep working.Decisions for the reviewer
User-visible copy changes
Tests changed
modal-accessibility.test.tsx: dialog focus and Enter tests now assert Cancel-first focus. The "confirms on Enter from a target with no activation of its own" test became "does not confirm".providers.test.tsxanderror-boundary.test.tsx: a non-API exception shows the fixed line, not its raw message.Verification
pnpm lint,pnpm typecheck,pnpm buildandpnpm testpass: 72 files, 685 tests.javascript:,data:, control-character and protocol-relative tricks.🤖 Generated with Claude Code