Skip to content

perf: the users page renders one edit modal, not one per user - #74

Merged
FMSMITH91 merged 2 commits into
mainfrom
perf/one-edit-modal-not-one-per-user
Aug 9, 2026
Merged

perf: the users page renders one edit modal, not one per user#74
FMSMITH91 merged 2 commits into
mainfrom
perf/one-edit-modal-not-one-per-user

Conversation

@FMSMITH91

Copy link
Copy Markdown
Owner

The last thing the benchmark was still pointing at. /users emitted 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.

before after
HTML 670.8 KB 198.1 KB
wire (gzipped) 29.3 KB 15.5 KB
render 6.9 ms 5.3 ms

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

  • 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's open leaves Bootstrap's backdrop stranded.

openEditUser sets 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

  • The Jinja-in-<script> guard now skips type="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_test now 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

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>
@codacy-production

codacy-production Bot commented Aug 9, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 15 complexity · 0 duplication

Metric Results
Complexity 15
Duplication 0

View in Codacy

🟢 Coverage ∅ diff coverage · -0.08% coverage variation

Metric Results
Coverage variation -0.08% coverage variation (-1.00%)
Diff coverage diff coverage

View coverage diff in Codacy

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.

Comment thread static/js/manage_users.js Fixed
…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.
@FMSMITH91
FMSMITH91 merged commit c8dfe50 into main Aug 9, 2026
19 checks passed
@FMSMITH91
FMSMITH91 deleted the perf/one-edit-modal-not-one-per-user branch August 9, 2026 17:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants