perf: the users page renders one edit modal, not one per user - #74
Merged
Conversation
The last thing the benchmark was still pointing at. /users emitted a complete
2KB edit dialog for every account — 2,498 bytes of markup per row, of which
2,117 was a modal you can only ever have one of open.
HTML 670.8 KB -> 198.1 KB
wire 29.3 KB -> 15.5 KB
render 6.9 ms -> 5.3 ms
The rows now carry `data-action="openEditUser"` with the account id, and the
data comes from a single JSON island — 172 bytes per user instead of 2,498,
built with |tojson so a quote or a bracket in a display name is escaped by
the serialiser rather than by hand.
Placement is deliberate. The island sits INSIDE #users-list so the ajax
refresh after a save keeps it current; the modal sits OUTSIDE it, because
replacing a modal while it is open leaves Bootstrap's backdrop behind.
openEditUser sets the form action per user, fills the fields, ticks the group
boxes by id, shows the 2FA block only for an account that has it, and clears
the password and the reset-2fa toggle every time so nothing carries over from
the last account you opened.
Six checks, and they assert the DATA as well as the shape: one modal, one
island entry per account, every entry matching that account's real flags and
groups, an Edit button per row, and no password anywhere in the island. A
smaller page that opened the wrong user's details would be a far worse bug
than a big one. Mutation-verified both ways.
Two supporting changes. The Jinja-in-<script> guard now skips
type="application/json": a data island is not JavaScript, nothing parses it as
JS, so Jinja inside it cannot cause the syntax error that check exists to
catch. And smoke_test reports a mid-run crash as a FAILURE — it had quietly
swallowed three of my own mistakes today, each appearing only as a lower
check count.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 15 |
| Duplication | 0 |
🟢 Coverage ∅ diff coverage · -0.08% coverage variation
Metric Results Coverage variation ✅ -0.08% coverage variation (-1.00%) Diff coverage ✅ ∅ diff coverage Coverage variation details
Coverable lines Covered lines Coverage Common ancestor commit (4f7b255) 11965 5886 49.19% Head commit (f62f0a5) 11965 (+0) 5877 (-9) 49.12% (-0.08%) Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch:
<coverage of head commit> - <coverage of common ancestor commit>Diff coverage details
Coverable lines Covered lines Diff coverage Pull request (#74) 0 0 ∅ (not applicable) Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified:
<covered lines added or modified>/<coverable lines added or modified> * 100%
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
…ough-dom) CodeQL flagged the id flowing from DOM text into a form action, rated high because an action is a URL sink. The value is always an integer from our own database, but it arrives as text read out of the DOM and nothing said so. parseInt proves it cannot carry meta-characters and rejects a tampered island outright.
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.
The last thing the benchmark was still pointing at.
/usersemitted a complete 2 KB edit dialog for every account — 2,498 bytes of markup per row, of which 2,117 was a modal you can only ever have one of open.Rows now carry
data-action="openEditUser"with the account id, and the data comes from a single JSON island — 172 bytes per user instead of 2,498 — built with|tojson, so a quote or bracket in a display name is escaped by the serialiser rather than by hand.Placement is deliberate
#users-list, so the ajax refresh after a save keeps it current.openEditUsersets the form action per user, fills the fields, ticks group boxes by id, shows the 2FA block only for an account that has it, and clears the password and reset-2fa toggle every time — so nothing carries over from the last account you opened.Tests assert the data, not just the shape
Six checks: one modal, one island entry per account, every entry matching that account's real flags and groups, an Edit button per row, and no password anywhere in the island. A smaller page that opened the wrong user's details would be a far worse bug than a big one.
Mutation-verified both ways — restoring per-user modal ids fails one check, emptying the groups list fails another.
Two supporting changes
<script>guard now skipstype="application/json". A data island isn't JavaScript; nothing parses it as JS, so Jinja inside it can't cause the syntax error that check exists to catch.smoke_testnow reports a mid-run crash as a failure. It had quietly swallowed three of my own mistakes today, each showing up only as a lower check count.unit 889 · smoke 307 → 313 · rbac 65 · manage 21 · template_actions 7 · lint clean.
🤖 Generated with Claude Code