diff --git a/README.md b/README.md index 48dd60e..242277d 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,13 @@ find the hostname in it yourself. The certificates and their provenance are documented in [`certs/PROVENANCE.md`](certs/PROVENANCE.md), including the command each was captured with. +The address input accepts ASCII DNS hosts and `scheme://authority` URLs, with +userinfo interpreted only inside the authority. An `@` in a path, query or +fragment does not change the hostname. This is a bounded teaching parser, not +the complete WHATWG URL implementation. A mismatched-address badge preserves +the pasted string and wraps long fragments or query values without widening +the page, including at the 320px reflow floor. + ## Exhibits The **guided lesson is the default**: five steps, one question and one action each, with an diff --git a/e2e/claims.spec.ts b/e2e/claims.spec.ts index d16ab37..0fd446f 100644 --- a/e2e/claims.spec.ts +++ b/e2e/claims.spec.ts @@ -166,6 +166,29 @@ test.describe('promise 2, the name check, re-derived independently', () => { }) } + for (const width of [320, 390, 1280]) { + test(`pasted URL badges preserve their complete text without page overflow at ${width}px`, async ({ page }) => { + await page.setViewportSize({ width, height: 900 }) + await bootExplore(page) + for (const address of [ + 'https://other.example/#user@github.com', + 'https://other.example/?email=user@github.com', + `https://other.example/?tracking=${'x'.repeat(200)}@github.com`, + ]) { + const expected = new URL(address).hostname === 'github.com' ? 'pass' : 'fail' + await page.locator('#address-input').fill(expected === 'pass' ? 'other.example' : 'github.com') + await expect(page.locator('[data-verdict="promise-name"]')).toHaveAttribute('data-result', expected === 'pass' ? 'fail' : 'pass') + await page.locator('#address-input').fill(address) + await expect(page.locator('[data-verdict="promise-name"]')).toHaveAttribute('data-result', expected) + const badge = page.locator('.badges .badge').filter({ hasText: address }) + await expect(badge).toBeVisible() + await expect(badge).toContainText(address) + const geometry = await page.evaluate(() => ({ document: document.documentElement.scrollWidth, viewport: innerWidth })) + expect(geometry.document).toBeLessThanOrEqual(geometry.viewport + 1) + } + }) + } + test('a failing name check names the actual cause', async ({ page }) => { await bootExplore(page) await page.locator('#address-input').fill('evil.example') diff --git a/src/style.css b/src/style.css index 9a84f83..e82b6b6 100644 --- a/src/style.css +++ b/src/style.css @@ -471,6 +471,7 @@ details > .details-body { padding: 0 0.8rem 0.8rem; } .badges { display: flex; flex-wrap: wrap; gap: 0.35rem; margin: 0.6rem 0 0; padding: 0; list-style: none; } .badge { font-family: var(--mono); font-size: 0.72rem; font-weight: 700; letter-spacing: 0.03em; + overflow-wrap: anywhere; padding: 0.2rem 0.5rem; border: 1px solid var(--alarm-ink); border-radius: 999px; color: var(--alarm-ink); background: var(--alarm-wash); }