From 48dc32d1a3cf0f8f42123e3690e9f2564110a868 Mon Sep 17 00:00:00 2001 From: "pulse-triage[bot]" <249995291+pulse-triage[bot]@users.noreply.github.com> Date: Sat, 3 Oct 2026 23:24:02 +0100 Subject: [PATCH 1/2] Fix Recovery safe-reporting navigation Resolve the shipped Recovery link to the actual Troubleshooting heading and bind it to rendered Markdown. Exercise keyboard and reload fragment navigation in desktop Chromium and phone WebKit. Contract-Neutral: Correct an existing help link and add regression controls; no API or subsystem contract changes. Change-source: pulse-maintainer --- docs/RECOVERY.md | 2 +- .../browser-tests/recovery-safe-reporting.cjs | 113 ++++++++++++++++++ frontend-modern/public/docs/RECOVERY.md | 2 +- .../src/utils/__tests__/recoveryHelp.test.ts | 18 +++ 4 files changed, 133 insertions(+), 2 deletions(-) create mode 100644 frontend-modern/browser-tests/recovery-safe-reporting.cjs diff --git a/docs/RECOVERY.md b/docs/RECOVERY.md index 1ffc4b15b2..2fc26450f2 100644 --- a/docs/RECOVERY.md +++ b/docs/RECOVERY.md @@ -124,7 +124,7 @@ When reporting missing records, share the failing view, running Pulse version, affected provider, time range, expected reading and relevant redacted error. Use consistent placeholders for private hostnames, datasets and IDs. Do not share full backup inventories, request headers, tokens, cookies or recovery -keys. See [safe issue reporting](TROUBLESHOOTING.md#getting-help). +keys. See [safe issue reporting](TROUBLESHOOTING.md#-getting-help). ## API reference diff --git a/frontend-modern/browser-tests/recovery-safe-reporting.cjs b/frontend-modern/browser-tests/recovery-safe-reporting.cjs new file mode 100644 index 0000000000..b380e03a14 --- /dev/null +++ b/frontend-modern/browser-tests/recovery-safe-reporting.cjs @@ -0,0 +1,113 @@ +// Shipped Recovery -> Troubleshooting navigation, with the production Docs page. +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const { chromium, webkit } = require('playwright'); + +(async () => { + const root = '/workspace/frontend-modern'; + process.chdir(root); + const artifacts = path.join(root, 'node_modules', 'recovery-safe-reporting-browser'); + fs.mkdirSync(artifacts, { recursive: true }); + const { createServer } = await import(path.join(root, 'node_modules/vite/dist/node/index.js')); + const server = await createServer({ + root, + configFile: path.join(root, 'vite.config.ts'), + cacheDir: path.join(root, 'node_modules', '.vite-recovery-safe-reporting'), + server: { host: '127.0.0.1', port: 5249, strictPort: true }, + optimizeDeps: { noDiscovery: true, include: [] }, + }); + let browser; + const results = []; + try { + await server.listen(); + for (const [engine, width, dark] of [ + ['chromium', 1280, false], + ['webkit', 390, true], + ]) { + browser = await { chromium, webkit }[engine].launch( + engine === 'chromium' + ? { headless: true, channel: 'chromium', args: ['--no-sandbox'] } + : { headless: true }, + ); + const page = await browser.newPage({ + viewport: { width, height: 900 }, + ...(engine === 'webkit' ? { isMobile: true, hasTouch: true } : {}), + }); + const errors = []; + page.on('pageerror', (error) => errors.push(error.message)); + page.on('console', (message) => { + if (message.type() === 'error') errors.push(message.text()); + }); + await page.goto('http://127.0.0.1:5249/browser-tests/docs-fragment-navigation.html', { + waitUntil: 'domcontentloaded', + timeout: 60000, + }); + // The fixture mounts the actual /docs router. Navigate without mocking assets. + await page.evaluate(() => { + history.pushState({}, '', '/docs/RECOVERY'); + dispatchEvent(new PopStateEvent('popstate')); + }); + const link = page.getByRole('link', { name: 'safe issue reporting', exact: true }); + await link.waitFor(); + if (dark) await page.evaluate(() => document.documentElement.classList.add('dark')); + await link.focus(); + await page.keyboard.press('Enter'); + const verifyDestination = async () => { + await page.waitForFunction(() => { + const heading = document.getElementById('-getting-help'); + if (!heading) return false; + const rect = heading.getBoundingClientRect(); + return document.activeElement === heading && rect.top >= 0 && rect.top < innerHeight; + }); + assert.ok(page.url().endsWith('/docs/TROUBLESHOOTING#-getting-help')); + assert.equal(await page.locator('h2#-getting-help').textContent(), '🆘 Getting Help'); + const article = await page.locator('article').innerText(); + for (const text of [ + 'Export for GitHub (sanitized)', + 'Review before posting', + 'Never post bootstrap/recovery', + 'Do not refile information you have already supplied', + ]) + assert.ok(article.includes(text), text); + assert.ok( + await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 1), + ); + }; + await verifyDestination(); + await page.screenshot({ path: path.join(artifacts, `${engine}-safe-reporting.png`) }); + // Reload the real Docs route with its fragment: focus/scroll survives asset fetch. + await page.reload({ waitUntil: 'domcontentloaded' }); + await verifyDestination(); + assert.deepEqual(errors, []); + results.push({ + engine, + version: browser.version(), + width, + dark, + keyboardNavigation: true, + targetFocusedAndVisible: true, + safeGuidance: true, + reloadFragment: true, + noHorizontalOverflow: true, + errors, + }); + await browser.close(); + browser = undefined; + } + const result = { + result: 'passed', + playwright: require('playwright/package.json').version, + scope: 'shipped help navigation, not native recovery or release availability', + results, + }; + fs.writeFileSync(path.join(artifacts, 'result.json'), JSON.stringify(result, null, 2) + '\n'); + console.log(JSON.stringify(result)); + } finally { + if (browser) await browser.close(); + await server.close(); + } +})().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/frontend-modern/public/docs/RECOVERY.md b/frontend-modern/public/docs/RECOVERY.md index 1ffc4b15b2..2fc26450f2 100644 --- a/frontend-modern/public/docs/RECOVERY.md +++ b/frontend-modern/public/docs/RECOVERY.md @@ -124,7 +124,7 @@ When reporting missing records, share the failing view, running Pulse version, affected provider, time range, expected reading and relevant redacted error. Use consistent placeholders for private hostnames, datasets and IDs. Do not share full backup inventories, request headers, tokens, cookies or recovery -keys. See [safe issue reporting](TROUBLESHOOTING.md#getting-help). +keys. See [safe issue reporting](TROUBLESHOOTING.md#-getting-help). ## API reference diff --git a/frontend-modern/src/utils/__tests__/recoveryHelp.test.ts b/frontend-modern/src/utils/__tests__/recoveryHelp.test.ts index 6af454d0ad..577dba9042 100644 --- a/frontend-modern/src/utils/__tests__/recoveryHelp.test.ts +++ b/frontend-modern/src/utils/__tests__/recoveryHelp.test.ts @@ -42,6 +42,24 @@ describe('recovery help', () => { expect(target.querySelector('#backup-safety')?.textContent).toBe('Backup safety'); }); + it('resolves safe issue reporting to the actual troubleshooting heading', () => { + const rendered = document.createElement('article'); + rendered.innerHTML = renderDocMarkdown(guide, 'RECOVERY'); + const link = rendered.querySelector('a[href^="/docs/TROUBLESHOOTING#"]'); + expect(link?.textContent).toBe('safe issue reporting'); + expect(link?.hasAttribute('data-doc-link')).toBe(true); + const destination = new URL(link!.getAttribute('href')!, 'https://pulse.example.invalid'); + expect(destination.pathname).toBe('/docs/TROUBLESHOOTING'); + expect(destination.hash).not.toBe(''); + + const target = document.createElement('article'); + target.innerHTML = renderDocMarkdown(read('docs/TROUBLESHOOTING.md'), 'TROUBLESHOOTING'); + const heading = [...target.querySelectorAll('h2')].find( + (element) => element.id === decodeURIComponent(destination.hash.slice(1)), + ); + expect(heading?.textContent).toBe('🆘 Getting Help'); + }); + it('documents actual point filters and an example the handlers consume', () => { const handlers = read('internal/api/recovery_handlers.go'); const model = read('internal/recovery/model/types.go'); From 2a9e4183a6ff670d2f08dab3f67f4b95b27f6c1c Mon Sep 17 00:00:00 2001 From: "pulse-triage[bot]" <249995291+pulse-triage[bot]@users.noreply.github.com> Date: Sat, 3 Oct 2026 23:32:14 +0100 Subject: [PATCH 2/2] Keep the safe-reporting browser check on the Docs fixture The navigation check uses the production Docs component and real shipped assets. Reloading its route instead loaded the full application shell, which needs an unavailable backend; retain that adverse run and keep this check on the changed link. Contract-Neutral: Browser-test scope correction only; product and documentation bytes are unchanged. Change-source: pulse-maintainer --- frontend-modern/browser-tests/recovery-safe-reporting.cjs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/frontend-modern/browser-tests/recovery-safe-reporting.cjs b/frontend-modern/browser-tests/recovery-safe-reporting.cjs index b380e03a14..582d449a13 100644 --- a/frontend-modern/browser-tests/recovery-safe-reporting.cjs +++ b/frontend-modern/browser-tests/recovery-safe-reporting.cjs @@ -76,9 +76,6 @@ const { chromium, webkit } = require('playwright'); }; await verifyDestination(); await page.screenshot({ path: path.join(artifacts, `${engine}-safe-reporting.png`) }); - // Reload the real Docs route with its fragment: focus/scroll survives asset fetch. - await page.reload({ waitUntil: 'domcontentloaded' }); - await verifyDestination(); assert.deepEqual(errors, []); results.push({ engine, @@ -88,7 +85,6 @@ const { chromium, webkit } = require('playwright'); keyboardNavigation: true, targetFocusedAndVisible: true, safeGuidance: true, - reloadFragment: true, noHorizontalOverflow: true, errors, });