Skip to content

fix(frontend): drop the last native dialogs in API key and users flows - #1361

Merged
IAmJSD merged 3 commits into
LeanerCloud:mainfrom
IAmJSD:ui/issue-705
Oct 24, 2026
Merged

IAmJSD merged 3 commits into
LeanerCloud:mainfrom
IAmJSD:ui/issue-705

Conversation

@IAmJSD

@IAmJSD IAmJSD commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator

Most of this issue was already fixed on main by #763 (23603381): the shared confirm dialog focuses Cancel for destructive actions, has aria-labelledby/aria-describedby, handles Shift+Tab, and the native confirm()/prompt() calls in navigation, bulk add-to-group and registration reject use the styled dialog. This PR removes the two native dialogs that were left (an alert() when copying a new API key fails, and a prompt() wired to a bulk "Add to group" button that no longer exists) and fixes the clipped curl example in the same API Key Created modal.

Closes #705

What changed

  • ddd3cbd0, frontend/src/apikeys.ts: the one-time "API Key Created" modal gets an inline <p id="apikey-copy-error" class="field-error" role="alert">. On a clipboard failure it shows "Could not copy automatically. The key is selected: press Ctrl+C (Cmd+C on Mac) to copy it." and selects the key text, instead of alert(). A later successful copy hides the message.
  • 99fa40e1, frontend/src/users/handlers.ts: removes the dead #bulk-group-btn prompt() handler and its unused import. That button is not in index.html; bulk add-to-group goes through the existing #bulk-group-select dropdown. The test now asserts a stray legacy button stays inert and never opens a native prompt.
  • 870a02d3, frontend/src/styles/modals.css: the curl example in the same modal (<pre class="code-example">) had no CSS rule, so the one-line command ran past the modal's right edge and was clipped at "https:/". A new .modal-content .code-example rule wraps it (white-space: pre-wrap; overflow-wrap: anywhere) and gives it the look of the existing .code-block snippet (monospace, light background, border, padding, radius, max-width: 100%, border-box). Stylesheet only, so the CSP is unaffected.
  • Tests: updated __tests__/apikeys.test.ts and __tests__/users.test.ts, and new tests-e2e/apikey-copy-failure.spec.ts.

Why this approach

An inline message inside the modal works better than a toast here: the modal is a require-ack focus trap over a backdrop, and selecting the key gives a one-keystroke fallback without leaving it. It reuses the existing .field-error style. For the prompt(), the button exists only in a jest fixture, and #763 already called it dead code left for cleanup; removing it is smaller and more honest than building a styled group picker for a button nobody can click.

Screenshots

Before is main (a14dc8f2), after is this branch. Both are headless Chromium at 1440x900 against the repo's mocked e2e API.

Before After
apikey-copy-fail before apikey-copy-fail after
apikey-curl before apikey-curl after
  • Copy failure: main pops a native alert() (the white banner at the top is drawn by the screenshot script to show the alert text, since native dialogs don't appear in page screenshots). After, no native dialog: a red inline message under the key says copying failed and the key is already selected. These two shots were taken before the curl commit, so they still show the clipped curl line.
  • Curl example: main runs past the modal's right edge and is cut off at "https:/". After, it wraps inside a light code block, so the whole command, including the endpoint, is visible.

Verification

Run on this branch's head 870a02d3 on top of main a14dc8f2:

  • Lint: 0 errors (141 existing warnings; the touched files have 2 existing any warnings). Typecheck and build pass.
  • Jest: 109 suites, 3485 passed, 1 skipped. Updated tests: apikeys "copy failure shows an inline error and selects the key, not a native alert" and users "never falls back to a native prompt() for bulk add to group". With --randomize, apikeys passes; users.test.ts fails 26-27 tests under seed -397418023, and the untouched base file fails 27 under the same seed, so that order dependence predates this branch.
  • Playwright via e2e-local.sh: 160 passed. The new apikey-copy-failure.spec.ts rejects the clipboard and asserts the inline role="alert" error, the selected key text and no native dialog; two more tests check at 1440x900 and 390x844 that the rendered curl text ends inside the modal's right edge and that neither the block nor the modal scrolls sideways. Both curl tests fail with the rule removed (text right edge 1097 vs modal 970 at 1440x900; 627 vs 390 at 390x844).
  • git-secrets scan clean (planted key flagged); scripts/test-git-secrets-allowlist.sh: 137 pass, 0 fail.
  • No native alert(), confirm() or prompt() calls remain in frontend/src outside tests.

Notes for review

  • Not done: the issue's "reuse openModal" suggestion for confirmDialog.ts. It already has its own working focus trap, Escape handling, focus restore and ARIA, so a refactor would change no behavior.
  • The copy-failure text is new copy and open to rewording.
  • The API key modal still looks partly unstyled on main (warning box, key row), as the screenshots show. Only the clipped curl line is fixed here.

Overlaps

Note for the UI owner

This changes UI code that @cristim owns. IAmJSD reviewed the before/after screenshots and asked for it to be merged; any design point you disagree with can be a follow-up.

🤖 Generated with Claude Code

IAmJSD and others added 3 commits October 23, 2026 22:12
When the clipboard write fails on the one-time "API Key Created" modal, the
app popped a browser-native alert(). Native dialogs cannot be styled, are
blocked in some embedded contexts, and sit outside the modal's focus
handling. The modal now shows an inline role="alert" message under the key
and selects the key text, so Ctrl+C / Cmd+C copies it without leaving the
modal. A later successful copy hides the message again.

Refs LeanerCloud#705

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
setupUserHandlers still wired #bulk-group-btn to a native prompt() asking
for a group ID. That button is not in index.html: the production toolbar
adds users to a group through #bulk-group-select. The handler was the last
native prompt() in the app, so it is removed and the test now asserts a
stray legacy button stays inert and never opens a native prompt.

Refs LeanerCloud#705

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The "API Key Created" dialog shows a curl example in a
<pre class="code-example">, but .code-example had no CSS rule. A <pre>
never wraps by default, so the one-line command ran past the modal's
right edge and was clipped at "https:/", hiding the endpoint part.

Add a .modal-content .code-example rule in modals.css next to the other
modal styles: it wraps (white-space: pre-wrap; overflow-wrap: anywhere)
so the whole command is visible without scrolling sideways, and gets
the same code-block look as the existing .code-block snippet (monospace,
light background, border, padding, radius). Stylesheet only, no inline
styles, so the CSP is unaffected.

The Playwright spec checks at 1440x900 and 390x844 that the rendered
command text ends inside the modal's right edge and that neither the
block nor the modal scrolls sideways. Both checks fail without the rule.

Refs LeanerCloud#705

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@IAmJSD IAmJSD added priority/p2 Backlog-worthy triaged Item has been triaged severity/high Significant harm urgency/this-quarter Within the quarter impact/all-users Affects every user effort/m Days type/bug Defect labels Oct 24, 2026
@coderabbitai

coderabbitai Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

  • Run on-demand review

This review includes 6 billable files and costs up to $1.50.

Or wait 59 minutes for your next included review.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration
Commits

Reviewing files that changed from the base of the PR and between 61be5b6 and 870a02d.

Files selected for processing: 6
  • frontend/src/__tests__/apikeys.test.ts
  • frontend/src/__tests__/users.test.ts
  • frontend/src/apikeys.ts
  • frontend/src/styles/modals.css
  • frontend/src/users/handlers.ts
  • frontend/tests-e2e/apikey-copy-failure.spec.ts
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@IAmJSD

IAmJSD commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): APPROVE 870a02d37819f8e9fb06efe37c169662daace85e

Blocking:

  • none
    Non-blocking:
  • frontend/src/apikeys.ts:453: pre-existing. In a non-secure context navigator.clipboard is undefined, so writeText throws synchronously, the new inline error never appears and the click does nothing. A guard that falls back to the select-and-message path would cover the most common way copying fails.
  • The role=alert

    is reused. A second failure with the same text while it is already visible may not be announced again.

  • The issue's "reuse openModal in confirmDialog" suggestion isn't done (the body says so). Its other points were fixed on main by fix(frontend): confirm dialog focuses the safe action and is accessible #763, so Closes fix(frontend): confirm dialog focuses destructive button, lacks a11y, native dialogs remain #705 is reasonable.
    Attacked: Grepped src for any remaining native alert/confirm/prompt calls (none outside comments) and for #bulk-group-btn in index.html and templates (absent, so removing the handler is safe). The bulk dropdown path is untouched. The new CSS rule only uses existing --cudly-* variables and adds no inline style. The key is still escaped in both template spots. Checked that a later successful copy hides the error.

@IAmJSD

IAmJSD commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

Local verification at 870a02d3 merged onto current main (67292303; merge-tree 1c22cb7a; Linux, Node, headless Chromium):

  • This branch's own checks passed at this head: lint, typecheck, build, the full jest suite, the full Playwright e2e suite, and the git-secrets checks (see Verification in the PR body).
  • The combined tree of every UI PR in this merge wave (tree 89dd34d8, main plus 46 PRs) passes npm ci, lint, typecheck, build, jest (1 skipped, 3914 passed, 3915 total), and the full Playwright suite (208 passed).
  • CI is green on 870a02d3 (CI Success, Run pre-commit hooks).
  • The independent review above approves this exact head.
  • Nothing was applied to any cloud.

@IAmJSD
IAmJSD marked this pull request as ready for review October 24, 2026 15:53
@IAmJSD
IAmJSD merged commit 512a7de into LeanerCloud:main Oct 24, 2026
29 checks passed
@IAmJSD

IAmJSD commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

Marked ready and merged by IAmJSD's agent, at IAmJSD's request (IAmJSD reviewed the before/after screenshots; cristim is traveling), after the independent review and local verification above, all at 870a02d3.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/m Days impact/all-users Affects every user priority/p2 Backlog-worthy severity/high Significant harm triaged Item has been triaged type/bug Defect urgency/this-quarter Within the quarter

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(frontend): confirm dialog focuses destructive button, lacks a11y, native dialogs remain

1 participant