Skip to content

fix(portfolio): keep the balance out of the aria labels in privacy mode - #8044

Open
yhabib wants to merge 4 commits into
mainfrom
fix/aria-labels-privacy-mode
Open

fix(portfolio): keep the balance out of the aria labels in privacy mode#8044
yhabib wants to merge 4 commits into
mainfrom
fix/aria-labels-privacy-mode

Conversation

@yhabib

@yhabib yhabib commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Motivation

Privacy mode masks token and staking balances on screen, but the aria-label on the balance cells still carried the raw number. A screen reader announced the hidden amount even while the display showed •••.

Changes

  • Made the USD cell label on the held tokens card follow privacy mode, announcing hidden instead of the number.
  • Made the USD and native stake cell labels on the staked tokens card follow privacy mode.
  • Fixed the staked native cell label, which used to carry the USD number under a D: typo instead of the native stake and its symbol.
  • Made the card header amount label follow privacy mode on both cards.
  • Added a portfolio.hidden_balance_label i18n key for the announced word.

Tests

  • Added unit tests for both cards asserting the aria-label text with privacy mode on and off, plus page-object getters for the new labels.
  • Kept the four existing privacy-masking tests passing.
  • Added an e2e spec, portfolio-privacy-aria-labels.spec.ts, that signs in, stakes ICP, toggles privacy mode, and sweeps every aria-label, title, and alt in the two cards for a leaked digit. It could not run locally: the replica would not come up with NNS canisters on this machine.
  • npm run check, CI=true npm run test, and ./scripts/check-relative-imports all pass.

Todos

  • Accessibility (a11y) – run the e2e spec above against a working replica, then check the Portfolio page with VoiceOver and NVDA, privacy mode on and off: each USD and native stake cell should announce its token, unit, and either the amount or "hidden". Also check whether either reader honours the aria-label on the header <p> amount, since ARIA prohibits a name on the paragraph role.
  • Changelog – already added under Application / Security in CHANGELOG-Nns-Dapp-unreleased.md.

The Portfolio cards masked the balance on screen but kept the exact
number in the `aria-label` of the cell. A screen reader announced the
number, and the DOM held it.

Each of the four labels now follows `isBalancePrivacyOptionStore`, the
same store the visible value follows. With privacy mode on the label
ends in `hidden`, so the cell keeps a usable name.

The staked native cell also labelled itself with the USD number and a
`D:` typo. It now names the native stake and its symbol.
The spec signs in a new user, gets ICP, stakes a neuron, and reads the
accessible names on the Portfolio cards with privacy mode off and on.
@yhabib
yhabib requested a review from a team as a code owner September 4, 2026 16:05
@yhabib
yhabib requested a lite review from Copilot September 4, 2026 16:05
@zeropath-ai

zeropath-ai Bot commented Sep 4, 2026

Copy link
Copy Markdown

No security or compliance issues detected. Reviewed everything up to 9724708.

Security Overview
Detected Code Changes
Change Type Relevant files
Enhancement ► frontend/src/lib/components/portfolio/HeldTokensCard.svelte
    Mask balance values when privacy option is enabled (aria-label updated to use hidden_balance_label)
Enhancement ► frontend/src/lib/components/portfolio/StakedTokensCard.svelte
    Mask balance values for USD and native currency in aria-labels when privacy option is enabled; introduce stakeNative computation for display and adjust aria-labels accordingly
Enhancement ► frontend/src/lib/components/portfolio/TokensCardHeader.svelte
    Mask displayed amount in aria-label when privacy option is enabled
Enhancement ► frontend/src/lib/i18n/en.json
    Add hidden_balance_label key with value "hidden"
Enhancement ► frontend/src/lib/types/i18n.d.ts
    Declare hidden_balance_label string in I18nPortfolio
Enhancement ► frontend/src/tests/e2e/portfolio-privacy-aria-labels.spec.ts
    Add end-to-end test for portfolio privacy aria-label behavior (includes new test logic for privacy toggle)
Enhancement ► frontend/src/tests/lib/components/portfolio/HeldTokensCard.spec.ts
    Add tests verifying aria-labels show balances when privacy off and hide when privacy on
Enhancement ► frontend/src/tests/lib/components/portfolio/StakedTokensCard.spec.ts
    Add tests verifying aria-labels for staked tokens show/hide balances based on privacy setting
Enhancement ► frontend/src/tests/page-objects/HeldTokensCard.page-object.ts
    Add methods to read aria-labels for balance and amount
Enhancement ► frontend/src/tests/page-objects/StakedTokensCard.page-object.ts
    Add methods to read aria-labels for stake USD/native and amount; add methods to read row-level aria-labels

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The updated header aria-label can still become ${title}: undefined when usdAmount is omitted (e.g., on “no tokens” cards), which is an accessibility bug that should be fixed before merging.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR closes a privacy-mode accessibility leak on the Portfolio page by ensuring balance-related aria-labels mirror the masked UI state (announcing a localized “hidden” label instead of the raw numeric amount).

Changes:

  • Updated Portfolio card header and balance-cell aria-labels to respect privacy mode, and corrected the native-stake label content in the staked tokens table.
  • Added a new i18n key (portfolio.hidden_balance_label) and updated types accordingly.
  • Added unit tests plus a new Playwright e2e spec to detect balance digits leaking via aria-label/title/alt attributes.
File summaries
File Description
frontend/src/tests/page-objects/StakedTokensCard.page-object.ts Adds getters to read new/updated balance-related aria-labels for tests.
frontend/src/tests/page-objects/HeldTokensCard.page-object.ts Adds getters to read USD row and header amount aria-labels for tests.
frontend/src/tests/lib/components/portfolio/StakedTokensCard.spec.ts Adds unit coverage for aria-label behavior with privacy mode on/off.
frontend/src/tests/lib/components/portfolio/HeldTokensCard.spec.ts Adds unit coverage for aria-label behavior with privacy mode on/off.
frontend/src/tests/e2e/portfolio-privacy-aria-labels.spec.ts Adds an end-to-end digit-leak scan over accessible-name-related attributes in Portfolio cards.
frontend/src/lib/types/i18n.d.ts Extends i18n typings with portfolio.hidden_balance_label.
frontend/src/lib/i18n/en.json Adds the new portfolio.hidden_balance_label string.
frontend/src/lib/components/portfolio/TokensCardHeader.svelte Makes the header amount aria-label privacy-aware.
frontend/src/lib/components/portfolio/StakedTokensCard.svelte Makes stake cell aria-labels privacy-aware and fixes the native-stake label content.
frontend/src/lib/components/portfolio/HeldTokensCard.svelte Makes the USD balance cell aria-label privacy-aware.
CHANGELOG-Nns-Dapp-unreleased.md Documents the privacy/a11y fix under Security.
Review details
  • Files reviewed: 10/11 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread frontend/src/lib/components/portfolio/TokensCardHeader.svelte
Comment thread frontend/src/tests/e2e/portfolio-privacy-aria-labels.spec.ts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

It changes user-facing accessibility behavior for a security/privacy concern and includes a known ARIA-spec compliance concern that needs manual screen-reader validation.

Review details
  • Files reviewed: 10/11 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread frontend/src/lib/components/portfolio/TokensCardHeader.svelte
Copilot AI previously approved these changes Sep 4, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approved

The changes consistently apply privacy mode to the relevant accessibility labels and are backed by targeted unit and e2e tests to prevent regressions.

Review details
  • Files reviewed: 10/11 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Escape did not close the account menu Popover: gix-components'
handleKeyPress only reacts to Enter and Space, not Escape. The menu
button toggles Popover visibility on each click, so click it again
to close it. This is what made the shard 1 CI job time out.
@yhabib
yhabib requested a lite review from Copilot September 4, 2026 16:34
Copilot AI dismissed their stale review, a newer Copilot review was requested September 4, 2026 16:34
Copilot AI previously approved these changes Sep 4, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approved

The changes consistently apply privacy-mode masking to accessible labels, are backed by unit tests (and an additional e2e spec), and do not introduce any verified regressions in the reviewed code paths.

Review details
  • Files reviewed: 10/11 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

The account-menu button click was blocked: the Popover backdrop covers
the whole screen, including the button, and intercepts pointer events.
Click the backdrop (which has its own click handler) instead.
@yhabib
yhabib requested a lite review from Copilot September 4, 2026 16:52
Copilot AI dismissed their stale review, a newer Copilot review was requested September 4, 2026 16:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approved

The changes directly address the described privacy leak with targeted unit/e2e coverage, and the remaining feedback is limited to minor e2e step-await best-practice improvements.

Review details

Suppressed comments (2)

Previously missed (1) — in code that hasn't changed since the last review.

frontend/src/tests/e2e/portfolio-privacy-aria-labels.spec.ts:44

  • step is an async helper (see frontend/src/tests/utils/e2e.test-utils.ts:11-20) that chains test.step calls via awaits. Calling it without await can start multiple steps concurrently and leave previous step promises unresolved, making step reporting flaky.

Consider awaiting each step(...) call (here and throughout the spec) so the step boundaries are deterministic.

This issue also appears on line 104 of the same file.

frontend/src/tests/e2e/portfolio-privacy-aria-labels.spec.ts:104

  • Same as above: this step(...) call should be awaited to preserve the intended sequencing of test.step blocks and avoid dangling step promises.
  step("Privacy mode off: every accessible name carries its amount");
  • Files reviewed: 10/11 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

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