From 691b60d1e5195ded641ad7cba5f6322bed67450f Mon Sep 17 00:00:00 2001 From: Derek Scruggs Date: Fri, 24 Jul 2026 17:10:42 -0500 Subject: [PATCH 1/3] Fix clipped "Allow Wallet" permission dialog. The first party permission popup opened at a fixed height of 230px, plus a 30px URL bar allowance applied only when the user agent looked like Firefox. Both parts of that guess were wrong: Chrome and Safari also show a URL bar in popups (and no user agent string predicts the real chrome height), and the greeting itself grows with the wallet's icon and name -- a site with a web app manifest renders a 48px icon above a name and origin line. The dialog is laid out to exactly fill the popup with one internally scrolling region, so content that did not fit was clipped mid-line rather than made scrollable, cutting off the wallet row. Apply the chrome allowance on every browser, raise the base height to 260, and have the popup measure its own content once loaded and resize to fit, replacing both guesses with a measurement. The sizing decision is a pure function (getWindowHeightAdjustment) clamped to the available screen height, a minimum usable height, and a maximum single adjustment; fitWindowToContent is the DOM shell around it and is a no-op where resizing is not permitted. Measured against a real cross-origin wallet, the greeting was clipped by 40px before this change and fits exactly after it. Also let the dev-only wallet chooser harness render an origin manifest, so it reproduces the taller greeting that the no-manifest default hid. --- CHANGELOG.md | 22 ++++ package.json | 1 + test/e2e/allow-wallet.spec.js | 136 ++++++++++++++++++++ test/unit/helpers.spec.js | 130 +++++++++++++++++++ web/components/FirstPartyMediatorWizard.vue | 42 +++++- web/mediator/constants.js | 17 ++- web/mediator/helpers.js | 126 +++++++++++++++++- web/routes/TestWalletChooser.vue | 42 +++++- 8 files changed, 505 insertions(+), 11 deletions(-) create mode 100644 test/e2e/allow-wallet.spec.js create mode 100644 test/unit/helpers.spec.js diff --git a/CHANGELOG.md b/CHANGELOG.md index d112b015..fb34731d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,27 @@ # authn.io ChangeLog +## 7.6.1 - 2026-07-dd + +### Fixed +- Stop clipping the first party "Allow Wallet" permission dialog. The + popup height was a fixed guess (230px, plus a 30px URL bar allowance + applied only when the user agent looked like Firefox), so the wallet + row was cut off mid-line whenever the browser's popup chrome or the + greeting's wrapping exceeded that guess. The dialog fills the popup and + scrolls internally, so this surfaced as clipped content rather than a + scrollable window. The allowance now applies to every browser (Chrome + and Safari also show a URL bar in popups, and user agent sniffing + cannot predict the real chrome height), and the popup measures its own + content once loaded and resizes to fit, replacing both guesses with a + measurement. + +### Added +- Non-API change: Add unit tests for the popup self-sizing logic and a + geometric-invariant test suite for the "Allow Wallet" dialog. +- Non-API change: Let the dev-only wallet chooser test harness render an + origin manifest (name and icon), so it reproduces the taller greeting + the real dialog shows for a site with a web app manifest. + ## 7.6.0 - 2026-07-23 ### Changed diff --git a/package.json b/package.json index 030b4238..60845b2a 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "start": "node authn.localhost.js", "lint": "eslint", "test:e2e": "playwright test wallet-chooser", + "test:unit": "node --test \"test/unit/*.spec.js\"", "gallery": "playwright test gallery && node test/e2e/build-gallery-index.js" }, "repository": { diff --git a/test/e2e/allow-wallet.spec.js b/test/e2e/allow-wallet.spec.js new file mode 100644 index 00000000..034b591e --- /dev/null +++ b/test/e2e/allow-wallet.spec.js @@ -0,0 +1,136 @@ +/*! + * New BSD License (3-clause) + * Copyright (c) 2026, Digital Bazaar, Inc. + */ +import {expect, test} from '@playwright/test'; + +/* Geometric-invariant tests for the first party "Allow Wallet" permission +dialog, exercised via the dev-only `/test/wallet-chooser` harness route with +`type=permissionRequest`. The bug these guard: the popup height was a fixed +guess (230px + a Firefox-only 30px URL-bar allowance), so on any browser whose +popup chrome or content wrapping exceeded that guess, the wallet row and the +Block/Allow footer were clipped off the bottom of the window. + +These assert the dialog fits — and that its interactive footer is reachable — +at the popup viewport heights the mediator actually requests, rather than +diffing pixels. */ + +/* Popup viewport heights to exercise. `DEFAULT_ALLOW_WALLET_POPUP_HEIGHT` is +290 (260 + a 30px chrome allowance), so the viewport the mediator actually +gets is 290 minus whatever chrome the browser adds. These bracket that range +down to the point where the content genuinely cannot fit and the popup relies +on `fitWindowToContent` growing it instead. + +Playwright pins the viewport, so `resizeBy` is inert here by design: these +tests assert the CSS/content invariant at a given height. The resize logic +itself is unit-tested via `getWindowHeightAdjustment` (see +`test/unit/helpers.spec.js`). */ +const HEIGHTS = [ + {name: 'requested height', height: 290}, + {name: 'chrome-reduced height', height: 260} +]; + +// A long origin/name exercises the text-wrapping cause of the clipping, +// independent of the popup-chrome cause. +const LONG_ORIGIN = + 'https://wallet-with-a-very-long-hostname.example-organization.test'; + +async function gotoAllowWallet(page, {origin} = {}) { + const query = new URLSearchParams({ + hints: '1', + type: 'permissionRequest', + qr: '0' + }); + if(origin) { + query.set('origin', origin); + } + await page.goto(`/test/wallet-chooser?${query}`); + await page.locator('.wrm-modal-1p .wrm-modal-content').waitFor(); +} + +for(const {name, height} of HEIGHTS) { + test.describe(`allow-wallet dialog @ ${name} (${height}px)`, () => { + test.use({viewport: {width: 500, height}}); + + test.beforeEach(async ({page}) => gotoAllowWallet(page)); + + test('shows the Block and Allow buttons', async ({page}) => { + await expect(page.getByRole('button', {name: 'Block'})).toBeVisible(); + await expect(page.getByRole('button', {name: 'Allow'})).toBeVisible(); + }); + + // The footer is pinned outside the scrolling body, so it stays in the + // viewport at every height; this guards that pinning (a regression that + // let it scroll away would be caught here), not the clipping bug. + test('keeps the Block/Allow footer within the viewport', + async ({page}) => { + for(const label of ['Block', 'Allow']) { + const box = await page.getByRole('button', {name: label}) + .boundingBox(); + expect(box, `${label} button has no box`).not.toBeNull(); + expect(box.y + box.height, + `${label} button bottom (${box.y + box.height}) exceeds ` + + `viewport height (${height})`).toBeLessThanOrEqual(height + 1); + } + }); + + /* The real clipping condition. The 1p panel is sized to exactly fill the + popup and its body region scrolls internally, so the *document* never + overflows and the pinned footer is always inside the viewport — neither a + document-level scroll check nor a footer-position check can detect this + bug. What the user sees instead is the greeting's wallet row cut off + mid-line inside that scrolling body. Assert the body does not need to + scroll: the popup must be tall enough for the greeting to fit whole. */ + test('does not clip the greeting inside the scrolling body', + async ({page}) => { + const overflow = await page.evaluate(() => { + const body = + document.querySelector('.wrm-modal-content-header + div'); + return body ? body.scrollHeight - body.clientHeight : null; + }); + expect(overflow, 'dialog body region not found').not.toBeNull(); + expect(overflow, + `dialog body overflows its box by ${overflow}px, clipping the ` + + 'wallet row').toBeLessThanOrEqual(1); + }); + + // the greeting's origin card must be fully within the scrolling body's + // visible box, not cut off at its bottom edge + test('shows the whole origin card', async ({page}) => { + const cut = await page.evaluate(() => { + const body = document.querySelector('.wrm-modal-content-header + div'); + const card = [...document.querySelectorAll('.wrm-flex-row')] + .find(el => !el.classList.contains('wrm-modal-content-header')); + if(!body || !card) { + return null; + } + return Math.round(card.getBoundingClientRect().bottom - + body.getBoundingClientRect().bottom); + }); + expect(cut, 'origin card or body not found').not.toBeNull(); + expect(cut, `origin card is cut off by ${cut}px`) + .toBeLessThanOrEqual(1); + }); + + test('keeps the origin row on one line', async ({page}) => { + // the origin/name must ellipsize rather than wrap; wrapping is what + // pushed the footer out of the popup + const wraps = await page.evaluate(() => { + const el = document.querySelector('.wrm-ellipsis'); + if(!el) { + return 'missing'; + } + return el.scrollHeight > el.clientHeight + 1; + }); + expect(wraps).toBe(false); + }); + + test('with a long origin, still fits', async ({page}) => { + await gotoAllowWallet(page, {origin: LONG_ORIGIN}); + const box = await page.getByRole('button', {name: 'Allow'}) + .boundingBox(); + expect(box).not.toBeNull(); + expect(box.y + box.height).toBeLessThanOrEqual(height + 1); + }); + }); +} diff --git a/test/unit/helpers.spec.js b/test/unit/helpers.spec.js new file mode 100644 index 00000000..75f90989 --- /dev/null +++ b/test/unit/helpers.spec.js @@ -0,0 +1,130 @@ +/*! + * New BSD License (3-clause) + * Copyright (c) 2026, Digital Bazaar, Inc. + */ +import assert from 'node:assert/strict'; +import {getWindowHeightAdjustment} from '../../web/mediator/helpers.js'; +import {test} from 'node:test'; + +/* Unit tests for the pure core of the popup self-sizing fix. The mediator +opens the allow-wallet popup at a fixed height, but the browser subtracts its +own chrome by an unpredictable amount and the greeting reflows with the +wallet's name and origin, so the popup measures itself once loaded and +corrects. `getWindowHeightAdjustment` is that decision, isolated from the DOM: +given what the content needs and what the window has, how much should the +window grow or shrink? + +Run with: node --test test/unit/ */ + +// a typical popup: 290px requested, ~60px of browser chrome +const TYPICAL = { + viewportHeight: 230, + outerHeight: 290, + availableHeight: 1000 +}; + +test('grows the window when content overflows', () => { + // the shipped bug: 263px of content in a 230px viewport + const adjustment = getWindowHeightAdjustment({ + ...TYPICAL, contentHeight: 263 + }); + assert.equal(adjustment, 33); +}); + +test('shrinks the window when it is taller than the content', () => { + const adjustment = getWindowHeightAdjustment({ + ...TYPICAL, contentHeight: 180 + }); + assert.equal(adjustment, -50); +}); + +test('does nothing when the content already fits exactly', () => { + const adjustment = getWindowHeightAdjustment({ + ...TYPICAL, contentHeight: 230 + }); + assert.equal(adjustment, 0); +}); + +test('ignores sub-pixel differences', () => { + // fractional layout metrics must not trigger a pointless resize + for(const contentHeight of [230.4, 229.6, 231, 229]) { + assert.equal(getWindowHeightAdjustment({...TYPICAL, contentHeight}), 0, + `contentHeight ${contentHeight} should not resize`); + } +}); + +test('never grows the viewport beyond the available screen height', () => { + /* 60px of chrome on a 700px screen leaves a 640px viewport, so a window + with a 230px viewport can grow by at most 410 -- but the per-call cap of + 400 binds first. */ + const adjustment = getWindowHeightAdjustment({ + viewportHeight: 230, + outerHeight: 290, + availableHeight: 700, + contentHeight: 5000 + }); + assert.equal(adjustment, 400); +}); + +test('respects the screen limit when it is tighter than the cap', () => { + // 40px chrome on a 400px screen => max viewport 360, so at most +130 + const adjustment = getWindowHeightAdjustment({ + viewportHeight: 230, + outerHeight: 270, + availableHeight: 400, + contentHeight: 5000 + }); + assert.equal(adjustment, 130); +}); + +test('never shrinks below the minimum usable height', () => { + // content claims almost nothing; the window must stay >= 160px + const adjustment = getWindowHeightAdjustment({ + ...TYPICAL, contentHeight: 10 + }); + assert.equal(adjustment, -70); + assert.equal(TYPICAL.viewportHeight + adjustment, 160); +}); + +test('bounds a single correction to the maximum adjustment', () => { + const adjustment = getWindowHeightAdjustment({ + viewportHeight: 800, + outerHeight: 860, + availableHeight: 4000, + contentHeight: 8000 + }); + assert.equal(adjustment, 400); +}); + +test('returns 0 when measurements are missing or unusable', () => { + const cases = [ + undefined, + {}, + {contentHeight: 300, viewportHeight: 0, outerHeight: 290}, + {contentHeight: 0, viewportHeight: 230, outerHeight: 290}, + {contentHeight: 300, viewportHeight: 230, outerHeight: 0} + ]; + for(const input of cases) { + assert.equal(getWindowHeightAdjustment(input), 0, + `expected 0 for ${JSON.stringify(input)}`); + } +}); + +test('tolerates a missing screen height', () => { + // `availableHeight` unknown: still grows, bounded by the per-call cap + const adjustment = getWindowHeightAdjustment({ + contentHeight: 263, viewportHeight: 230, outerHeight: 290 + }); + assert.equal(adjustment, 33); +}); + +test('handles a window reporting no chrome', () => { + // `outerHeight === innerHeight` (some embedded/headless contexts) + const adjustment = getWindowHeightAdjustment({ + contentHeight: 300, + viewportHeight: 230, + outerHeight: 230, + availableHeight: 1000 + }); + assert.equal(adjustment, 70); +}); diff --git a/web/components/FirstPartyMediatorWizard.vue b/web/components/FirstPartyMediatorWizard.vue index cd9220f5..3f0b9591 100644 --- a/web/components/FirstPartyMediatorWizard.vue +++ b/web/components/FirstPartyMediatorWizard.vue @@ -28,10 +28,39 @@ * New BSD License (3-clause) * Copyright (c) 2017-2026, Digital Bazaar, Inc. */ -import {computed, onMounted, ref, toRaw} from 'vue'; +import {computed, nextTick, onMounted, ref, toRaw} from 'vue'; import {FirstPartyMediator} from '../mediator/FirstPartyMediator.js'; +import {fitWindowToContent} from '../mediator/helpers.js'; import MediatorWizard from './MediatorWizard.vue'; +// give up waiting on a slow wallet icon rather than delay the resize; a late +// image just means the popup is corrected without its height +const IMAGE_LOAD_TIMEOUT = 1000; + +/* Resolves once every in the document has settled (loaded or errored), +or the timeout elapses. An icon that has not loaded reports no height, so +measuring before it settles under-reports the content. */ +async function _waitForImages() { + const images = [...document.querySelectorAll('img')] + .filter(img => !img.complete); + if(images.length === 0) { + return; + } + const settled = images.map(img => new Promise(resolve => { + img.addEventListener('load', resolve, {once: true}); + img.addEventListener('error', resolve, {once: true}); + })); + let timer; + const timeout = new Promise(resolve => { + timer = setTimeout(resolve, IMAGE_LOAD_TIMEOUT); + }); + try { + await Promise.race([Promise.all(settled), timeout]); + } finally { + clearTimeout(timer); + } +} + export default { name: 'FirstPartyMediatorWizard', components: {MediatorWizard}, @@ -118,6 +147,17 @@ export default { await mediator.credentialRequestOriginManifestPromise; credentialRequestOrigin.value = mediator.credentialRequestOrigin; loading.value = false; + + /* Correct the popup height now that the real content is known. + The height requested when opening the popup is a guess: the + browser subtracts its own chrome by an unpredictable amount and + the greeting reflows with the wallet's name and origin. Measuring + here replaces both guesses. Waits for the DOM update and for the + wallet icon to load, since an image that has not loaded yet does + not contribute its height. */ + await nextTick(); + await _waitForImages(); + fitWindowToContent(); } }); } catch(e) { diff --git a/web/mediator/constants.js b/web/mediator/constants.js index d7a22f39..d8a43818 100644 --- a/web/mediator/constants.js +++ b/web/mediator/constants.js @@ -2,13 +2,20 @@ * New BSD License (3-clause) * Copyright (c) 2017-2026, Digital Bazaar, Inc. */ -const addUrlBarHeight = navigator.userAgent.toLowerCase().includes('firefox'); -const urlBarHeight = addUrlBarHeight ? 30 : 0; +/* Popup chrome (URL bar, title bar) is subtracted from the height passed to +`window.open`, so the usable viewport is always shorter than requested by an +amount that varies by browser, platform, and user settings. This allowance +covers the common case; it is applied unconditionally rather than only when the +user agent looks like Firefox, because Chrome and Safari also show a URL bar in +popups and UA sniffing cannot predict the real chrome height. Popups that end +up with more viewport than they need are shrunk to fit after they load (see +`fitWindowToContent`), so over-allowing here costs nothing. */ +const urlBarHeight = 30; export const DEFAULT_ALLOW_WALLET_POPUP_WIDTH = 500; -// tall enough for the Block/Allow footer on browsers with tall popup -// chrome (e.g. Firefox always shows a URL bar in popups) -export const DEFAULT_ALLOW_WALLET_POPUP_HEIGHT = 230 + urlBarHeight; +// tall enough for the greeting (48px wallet icon + name and origin lines) +// plus the Block/Allow footer +export const DEFAULT_ALLOW_WALLET_POPUP_HEIGHT = 260 + urlBarHeight; export const DEFAULT_HANDLER_POPUP_WIDTH = 800; export const DEFAULT_HANDLER_POPUP_HEIGHT = 600; diff --git a/web/mediator/helpers.js b/web/mediator/helpers.js index 7254e55b..e70d92d2 100644 --- a/web/mediator/helpers.js +++ b/web/mediator/helpers.js @@ -1,8 +1,132 @@ /*! * New BSD License (3-clause) - * Copyright (c) 2017-2023, Digital Bazaar, Inc. + * Copyright (c) 2017-2026, Digital Bazaar, Inc. * All rights reserved. */ + +// never grow or shrink a popup past this, so pathological content cannot +// produce a full-screen window and rounding cannot collapse one +const MAX_POPUP_HEIGHT_ADJUSTMENT = 400; +const MIN_POPUP_HEIGHT = 160; + +/** + * Computes the height adjustment a popup needs so its content fits exactly. + * + * This is the pure core of `fitWindowToContent`: the mediator opens popups at + * a fixed height, but the browser subtracts its own chrome (URL bar, title + * bar) by an amount that varies across browsers, platforms, and settings, and + * the content itself reflows with the wallet name and origin length. Rather + * than predicting either, the popup measures itself once loaded and corrects. + * + * @param {object} options - The options to use. + * @param {number} options.contentHeight - Height the content needs (px). + * @param {number} options.viewportHeight - Current usable viewport (px). + * @param {number} options.outerHeight - Current total window height (px). + * @param {number} options.availableHeight - Usable screen height (px). + * + * @returns {number} Pixels to grow (positive) or shrink (negative) by; `0` + * when the window already fits or cannot be usefully changed. + */ +export function getWindowHeightAdjustment({ + contentHeight, viewportHeight, outerHeight, availableHeight +} = {}) { + // guard against a caller that cannot measure (e.g. a detached document) + if(!(contentHeight > 0 && viewportHeight > 0 && outerHeight > 0)) { + return 0; + } + + // ignore sub-pixel noise from fractional layout metrics + const deficit = Math.round(contentHeight - viewportHeight); + if(Math.abs(deficit) <= 1) { + return 0; + } + + // the chrome is whatever the window has beyond its viewport; it stays + // constant as the window resizes, so the largest viewport available is the + // screen minus that chrome + const chromeHeight = Math.max(0, outerHeight - viewportHeight); + const maxViewport = Math.max(0, (availableHeight || 0) - chromeHeight); + + let adjustment = deficit; + // never exceed the screen (a window taller than the display is worse than + // a scrolling one -- its footer would be unreachable) + if(maxViewport > 0) { + adjustment = Math.min(adjustment, maxViewport - viewportHeight); + } + // keep the window usable, and bound how far a single correction can move it + adjustment = Math.max(adjustment, MIN_POPUP_HEIGHT - viewportHeight); + adjustment = Math.max(-MAX_POPUP_HEIGHT_ADJUSTMENT, + Math.min(MAX_POPUP_HEIGHT_ADJUSTMENT, adjustment)); + + return Math.abs(adjustment) <= 1 ? 0 : adjustment; +} + +/** + * Resizes a script-opened popup so its content fits without clipping. + * + * The imperative shell around `getWindowHeightAdjustment`. Safe to call in a + * regular tab: `resizeBy` is a no-op on windows the script did not open, and + * any failure is swallowed -- a popup that cannot be resized still works, it + * just scrolls. + * + * @param {object} options - The options to use. + * @param {Window} [options.win=window] - The window to fit. + * + * @returns {number} The adjustment actually applied, in px. + */ +export function fitWindowToContent({win = window} = {}) { + try { + const {documentElement: html, body} = win.document; + /* The document's own `scrollHeight` is not enough. The 1p dialog is laid + out to exactly fill the popup with one internally scrolling region, so + content that does not fit is clipped *inside* that region and the document + itself never overflows -- `html.scrollHeight` equals `innerHeight` no + matter how badly the greeting is cut off. Add the largest overflow found + among scrolling descendants so the window grows by enough to reveal it. */ + const documentHeight = Math.max( + html.scrollHeight, body ? body.scrollHeight : 0); + const contentHeight = documentHeight + _getMaxInnerOverflow(win); + const adjustment = getWindowHeightAdjustment({ + contentHeight, + viewportHeight: win.innerHeight, + outerHeight: win.outerHeight, + availableHeight: win.screen && win.screen.availHeight + }); + if(adjustment !== 0) { + win.resizeBy(0, adjustment); + } + return adjustment; + } catch { + // cross-origin, no permission to resize, or nothing to measure + return 0; + } +} + +/* Largest vertical overflow among the document's scrollable elements. +Exported for testing only. + +The wallet hint list is intentionally scrollable and can be arbitrarily long, +so it is skipped: growing the window to fit every wallet is not the goal (the +hint chooser has its own taller popup height for that). What this looks for is +a region that is clipped only because the popup is too short for its fixed +content -- the greeting and footer. */ +export function _getMaxInnerOverflow(win) { + let max = 0; + const elements = win.document.querySelectorAll('.wrm-modal-content *'); + for(const el of elements) { + if(el.classList.contains('wrm-hint-list')) { + continue; + } + const overflow = el.scrollHeight - el.clientHeight; + // ignore elements with no content box (inline elements report 0/0) and + // sub-pixel noise + if(el.clientHeight > 0 && overflow > max) { + max = overflow; + } + } + return Math.round(max); +} + export function getOriginName({origin, manifest} = {}) { const {host} = new URL(origin); if(!manifest) { diff --git a/web/routes/TestWalletChooser.vue b/web/routes/TestWalletChooser.vue index 03e279b3..42d53b4c 100644 --- a/web/routes/TestWalletChooser.vue +++ b/web/routes/TestWalletChooser.vue @@ -2,7 +2,7 @@ route.query.origin || 'https://verifier.example'); + /* A manifest makes `WrmOriginCard` render its full form: the large icon + plus a name line stacked above the origin line, instead of the origin + alone. That taller card is what the real dialog shows for any site with a + web app manifest, so the no-manifest default under-reports the greeting's + height. `name=` sets the displayed name; `manifest=0` forces the bare + origin form. */ + const credentialRequestOriginManifest = computed(() => { + const {manifest, name} = route.query; + if(manifest === '0' || manifest === 'false') { + return null; + } + return { + name: name || 'Demo Wallet', + /* A loadable icon so the card renders at its real (tallest) height. + Without `icons`, the icon falls back to `/favicon.ico`, which + cannot load for a fake test origin and collapses to a small default + glyph — under-reporting the greeting height for any real wallet that + does have an icon. */ + icons: [{ + src: ICON_URL, + sizes: '48x48', + type: 'image/svg+xml' + }] + }; + }); + /* mirror `FirstPartyMediatorWizard`: the chooser is shown for every request type except a permission request (which shows the allow/deny greeting instead). This is what surfaces the chooser with zero hints. */ @@ -91,8 +125,8 @@ export default { const noop = () => {}; return { - credentialRequestOrigin, hints, interactionUrl, noop, requestType, - showHintChooser + credentialRequestOrigin, credentialRequestOriginManifest, hints, + interactionUrl, noop, requestType, showHintChooser }; } }; From bd31abf70952ad3b78c1194a1c5a7dada9238591 Mon Sep 17 00:00:00 2001 From: Derek Scruggs Date: Fri, 24 Jul 2026 17:38:48 -0500 Subject: [PATCH 2/3] Change request language on popup. --- web/components/MediatorGreeting.vue | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/web/components/MediatorGreeting.vue b/web/components/MediatorGreeting.vue index 3782582b..ceb1824f 100644 --- a/web/components/MediatorGreeting.vue +++ b/web/components/MediatorGreeting.vue @@ -57,8 +57,9 @@ export default { if(requestType.value === 'credentialStore') { return 'has credentials for you to store:'; } - return 'wants to appear as an option when you\'re asked to share or ' + - 'store information on other websites:'; + return 'This site wants to be a digital wallet option when ' + + 'other sites ask you to share or store information: '; + }); return {websiteDesire}; } From 724a1148dff0e2af6a2cf5112fd5170cd0884b86 Mon Sep 17 00:00:00 2001 From: Derek Scruggs Date: Fri, 24 Jul 2026 17:50:22 -0500 Subject: [PATCH 3/3] Changed "the following website" to "this site" --- web/components/MediatorGreeting.vue | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/web/components/MediatorGreeting.vue b/web/components/MediatorGreeting.vue index ceb1824f..d69c7e29 100644 --- a/web/components/MediatorGreeting.vue +++ b/web/components/MediatorGreeting.vue @@ -5,7 +5,7 @@
- The following website {{websiteDesire}} + This site {{websiteDesire}}