diff --git a/TREE.md b/TREE.md index 19bc9a7..ffdfd6b 100644 --- a/TREE.md +++ b/TREE.md @@ -348,6 +348,15 @@ research/evaluations/language-pilot-01/summarize.py research/evaluations/language-pilot-01/summary.json research/examples/SchemaEvidence.md research/examples/schema-contract.yaml +research/experiments/boundary-recovery/README.md +research/experiments/boundary-recovery/development-results.json +research/experiments/boundary-recovery/experiment.mjs +research/experiments/boundary-recovery/fixture.jsx +research/experiments/boundary-recovery/package-lock.json +research/experiments/boundary-recovery/package.json +research/experiments/boundary-recovery/provenance.json +research/experiments/boundary-recovery/result-immediate.json +research/experiments/boundary-recovery/result-settled.json research/experiments/matrix-recovery/README.md research/experiments/matrix-recovery/experiment.mjs research/experiments/matrix-recovery/package-lock.json @@ -359,6 +368,7 @@ research/plan/ResearchProgram.md research/plan/ResearchProtocol.md research/plan/ValidationCases.md research/reviews/AuthorityPilot.md +research/reviews/BoundaryRecoveryExperiment.md research/reviews/CarniatoAdmissionRegression.json research/reviews/FrontendRecoveryImplementations.md research/reviews/FrontendRecoverySources.json diff --git a/research/README.md b/research/README.md index 17137f0..528dc88 100644 --- a/research/README.md +++ b/research/README.md @@ -48,3 +48,7 @@ Latest expansion: [six broader candidates and decision probes](reviews/PatternBr cover presentation logic, derived state, feature organization, client activation, failure containment and modal focus. Current total: sixteen developed hypotheses; previous counts above describe earlier slices. No additional runtime or model runs. + +Latest execution: [rendering/focus boundary experiment](reviews/BoundaryRecoveryExperiment.md) +uses pinned React/Radix packages in Chromium. It records seven stable case observations +and a timing-sensitive nested-dialog failure; no production or accessibility certification. diff --git a/research/candidates/FailureBoundary.md b/research/candidates/FailureBoundary.md index 0ebf905..d818975 100644 --- a/research/candidates/FailureBoundary.md +++ b/research/candidates/FailureBoundary.md @@ -38,3 +38,11 @@ Evidence locators: [breadth review](../reviews/PatternBreadthExpansion.md). All relations are research proposals; no canonical identity or admitted graph edges. The next test is planned, not executed. Known uses of the complete proposed contract have not been verified in this investigation. + +## Executed partial evidence — 2026-09-26 + +[Boundary experiment](../reviews/BoundaryRecoveryExperiment.md) X01–X04 runs real +React 19.3.0 in a browser fixture. A render fault stays inside its region; an event +handler error does not; a handled 503 uses explicit request state; a broken fallback +reaches the enclosing boundary. These are bounded mechanism observations, not +production recurrence or evidence for mutation rollback. Retain hypothesis maturity. diff --git a/research/candidates/FocusReturn.md b/research/candidates/FocusReturn.md index e65b6f4..a53c180 100644 --- a/research/candidates/FocusReturn.md +++ b/research/candidates/FocusReturn.md @@ -40,3 +40,14 @@ Evidence locators: [breadth review](../reviews/PatternBreadthExpansion.md). All relations are research proposals; no canonical identity or admitted graph edges. The next test is planned, not executed. Known uses of the complete proposed contract have not been verified in this investigation. + +## Executed partial evidence — 2026-09-26 + +[Boundary experiment](../reviews/BoundaryRecoveryExperiment.md) X05–X08 runs Radix +Dialog 1.1.23. Ordinary return succeeds; removal of the trigger leaves BODY focused +unless the application supplies a surviving target. Nested dismissal is timing-sensitive: +immediate Escape failed the parent-return obligation in the final recorded immediate +run, while a 100 ms settling variant passed. Neither focus arrival nor dialog visibility +alone proves dismissal ownership has stabilized. The delay is an experimental variable, +not a recommended fix. Root cause and accessibility conformance remain unverified; +retain hypothesis maturity and test lifecycle ownership before a stronger claim. diff --git a/research/catalog/PatternCandidateCatalog.md b/research/catalog/PatternCandidateCatalog.md index 0681bd4..9689758 100644 --- a/research/catalog/PatternCandidateCatalog.md +++ b/research/catalog/PatternCandidateCatalog.md @@ -122,3 +122,11 @@ addition. Six further developed hypotheses bring the current total to **sixteen* non-overlap, counterexamples, synthetic probes and deferred candidates. Initial seed rows above are historical inventory, not additional developed records. No canonical admission, proven recurrence or demonstrated presentation benefit is claimed. + +## Partial boundary execution — 2026-09-26 + +[Browser experiment](../reviews/BoundaryRecoveryExperiment.md) adds partial evidence +for Failure Boundary and Focus Return. Seven of eight immediate-run assertions met; +a separate 100 ms nested-settling condition met eight. Removed-trigger default behavior +is a negative control, and immediate nested dismissal remains unresolved. Sixteen +hypotheses retained, no admission or full-pattern recurrence claim. diff --git a/research/catalog/SourceCatalog.md b/research/catalog/SourceCatalog.md index b2852c8..36b30c5 100644 --- a/research/catalog/SourceCatalog.md +++ b/research/catalog/SourceCatalog.md @@ -109,3 +109,16 @@ All accessed 2026-09-26; exact revisions unknown unless stated. Bounded document review, no runtime or deployment validation. [Claims B01–B06 and dispositions](../reviews/PatternBreadthExpansion.md). These sources address different mechanisms and do not establish independent recurrence of any one complete candidate. + +## Browser boundary execution — 2026-09-26 + +SRC-0029: React 19.3.0 / React DOM 19.3.0 and Radix Dialog 1.1.23, with pinned +transitive dependencies and inspected published modules. [Provenance](../experiments/boundary-recovery/provenance.json) +and lockfile identify archives/integrity; synthetic Chromium fixture, not a deployed +application. One mechanism study, not independent full-pattern recurrence. + +SRC-0030: [Radix Dialog documentation](https://www.radix-ui.com/primitives/docs/components/dialog), +Content API and keyboard interaction; living page accessed 2026-09-26, revision +unknown. Displayed documentation version differs from executed package; runtime +claims use the pinned archive, not an assumption that the page exactly matches it. +[Results and limits](../reviews/BoundaryRecoveryExperiment.md). diff --git a/research/experiments/boundary-recovery/README.md b/research/experiments/boundary-recovery/README.md new file mode 100644 index 0000000..6df069e --- /dev/null +++ b/research/experiments/boundary-recovery/README.md @@ -0,0 +1,63 @@ +# Rendering and focus boundary experiment + +Executed 2026-09-26. Real React 19.3.0 / Radix Dialog 1.1.23 in a synthetic browser +fixture; no deployed application, provider inference or production requests. +[Review and interpretation](../../reviews/BoundaryRecoveryExperiment.md). + +## Reproduce + +From this directory: + +```bash +npm ci --ignore-scripts --cache /tmp/fep-npm-cache +FEP_CHROMIUM_PATH=/path/to/chrome FEP_RESULT_PATH=/tmp/immediate.json npm test +FEP_CHROMIUM_PATH=/path/to/chrome FEP_NESTED_SETTLE_MS=100 FEP_RESULT_PATH=/tmp/settled.json npm test +``` + +Use a locally available Chromium executable, or omit `FEP_CHROMIUM_PATH` to use +Playwright's installed browser. The recorded runs used preinstalled Chromium +151.0.7922.34, not an assertion of compatibility with every browser. Version and +executable hash are in [provenance](provenance.json); the lockfile pins all packages +with archive integrity. Installation does not download a browser automatically here. +The runner binds an ephemeral loopback port, bundles the fixture in memory, and +closes browser/server in `finally`. No external network is used by the cases. + +Output defaults to `/tmp/fep-boundary-result.json`; reviewed receipts are not +overwritten automatically. A failed obligation makes the runner exit nonzero. +The immediate nested-dialog case is known to be timing-sensitive; do not hide it +with retries or interpret a later pass as its resolution. `FEP_NESTED_SETTLE_MS` +changes only the pause after nested activation before the first Escape. It is an +experimental variable, not a proposed product fix. The browser uses React's development +build, without StrictMode. Production builds and other browsers remain untested. + +## Recorded cases + +| Case | Observation | Immediate / 100 ms settled | +| --- | --- | --- | +| X01 | Render fault uses inner fallback; sibling stays interactive | pass / pass | +| X02 | Event-handler exception reaches browser error reporting, not boundary | pass / pass | +| X03 | Synthetic GET returns 503; explicit catch sets request-failure state | pass / pass | +| X04 | Fault in inner fallback reaches outer boundary | pass / pass | +| X05 | Closing modal returns focus to surviving invoker | pass / pass | +| X06 | Removing invoker leaves BODY focused with default behavior | pass / pass (negative control) | +| X07 | Application close policy selects surviving next-record button | pass / pass | +| X08 | Nested close should return to parent trigger, then outer trigger | fail / pass | + +[result-immediate.json](result-immediate.json) records 7/8; [result-settled.json](result-settled.json) +records 8/8 with identical code/input hashes, except the declared timing parameter. +“Pass” means an observation matched its assertion, not that the UX is acceptable: +X06 deliberately expects the missing workflow target. + +[development-results.json](development-results.json) preserves three earlier runs +in order: 7/8, 8/8, 7/8. They preceded final diagnostic instrumentation; their old +runner bytes are not archived, so they are development history, not exact reproducible +snapshots. Current-code receipts above retain their matching SHA-256 input hashes. +All five runs are reported; no estimate of failure frequency is justified. + +## Scope + +Focus checks inspect activeElement and keyboard behavior, not screen-reader output +or full accessibility conformance. X03 handles a response error explicitly; it does +not test unhandled promises, timeouts, mutation idempotency or remote rollback. +Nested dismissal remains unresolved. Dependency/version compatibility and fixture +lifecycle effects must be separated before attributing a library defect. diff --git a/research/experiments/boundary-recovery/development-results.json b/research/experiments/boundary-recovery/development-results.json new file mode 100644 index 0000000..46c4da0 --- /dev/null +++ b/research/experiments/boundary-recovery/development-results.json @@ -0,0 +1,333 @@ +[ + { + "date": "2026-09-26T19:57:23.882Z", + "evidence": "synthetic-browser-fixture-real-libraries", + "node": "v24.11.1", + "browser": "151.0.7922.34", + "browserSelection": "explicit executable override", + "packages": { + "react": "19.3.0", + "react-dom": "19.3.0", + "@radix-ui/react-dialog": "1.1.23", + "playwright": "1.63.0", + "esbuild": "0.28.2" + }, + "files": { + "fixture.jsx": "d01a78f533a8ac4c086fc93845ce5b86a5b676aee86a22ee53ac91ad2a77f936", + "experiment.mjs": "981d39a45d22503077817cb032145f1e05221821c157be18ca7a509667a5e384", + "package.json": "a0ad37e8518b0c561d6b4a091ee51f5bca90e0ce329b2be7ba75347f6dc26ae7", + "package-lock.json": "ec4b544d441f89d9c512bc9931c1818473327105d5ef9047dc04d6a721e68463" + }, + "cases": [ + { + "id": "X01-render-contained", + "status": "pass", + "observed": { + "caught": [ + { + "boundary": "inner", + "message": "render fault" + } + ], + "globalErrors": [], + "requests": [] + }, + "siblingUsable": true, + "browserErrors": [] + }, + { + "id": "X02-event-not-contained", + "status": "pass", + "observed": { + "caught": [], + "globalErrors": [ + "Uncaught Error: event fault" + ], + "requests": [] + }, + "panelPresent": true, + "browserErrors": [ + "event fault" + ] + }, + { + "id": "X03-request-explicit-state", + "status": "pass", + "observed": { + "caught": [], + "globalErrors": [], + "requests": [ + 503 + ] + }, + "state": "request failed", + "browserErrors": [] + }, + { + "id": "X04-fallback-escapes-inner", + "status": "pass", + "observed": { + "caught": [ + { + "boundary": "outer", + "message": "fallback fault" + } + ], + "globalErrors": [], + "requests": [] + }, + "enclosingFallback": true, + "browserErrors": [] + }, + { + "id": "X05-focus-existing-invoker", + "status": "pass", + "activeId": "invoker", + "browserErrors": [] + }, + { + "id": "X06-focus-removed-default", + "status": "pass", + "activeTag": "BODY", + "workflowTargetSelected": false, + "browserErrors": [] + }, + { + "id": "X07-focus-removed-explicit", + "status": "pass", + "activeId": "next", + "applicationPolicy": true, + "browserErrors": [] + }, + { + "id": "X08-nested-return", + "status": "fail", + "error": "page.waitForFunction: Timeout 5000ms exceeded.", + "browserErrors": [] + } + ], + "passed": 7, + "total": 8 + }, + { + "date": "2026-09-26T19:57:42.875Z", + "evidence": "synthetic-browser-fixture-real-libraries", + "node": "v24.11.1", + "browser": "151.0.7922.34", + "browserSelection": "explicit executable override", + "packages": { + "react": "19.3.0", + "react-dom": "19.3.0", + "@radix-ui/react-dialog": "1.1.23", + "playwright": "1.63.0", + "esbuild": "0.28.2" + }, + "files": { + "fixture.jsx": "d01a78f533a8ac4c086fc93845ce5b86a5b676aee86a22ee53ac91ad2a77f936", + "experiment.mjs": "5268d5cfefedb6186333bcc014080757821f2bb4820f70397b22474bd3264414", + "package.json": "a0ad37e8518b0c561d6b4a091ee51f5bca90e0ce329b2be7ba75347f6dc26ae7", + "package-lock.json": "ec4b544d441f89d9c512bc9931c1818473327105d5ef9047dc04d6a721e68463" + }, + "cases": [ + { + "id": "X01-render-contained", + "status": "pass", + "observed": { + "caught": [ + { + "boundary": "inner", + "message": "render fault" + } + ], + "globalErrors": [], + "requests": [] + }, + "siblingUsable": true, + "browserErrors": [] + }, + { + "id": "X02-event-not-contained", + "status": "pass", + "observed": { + "caught": [], + "globalErrors": [ + "Uncaught Error: event fault" + ], + "requests": [] + }, + "panelPresent": true, + "browserErrors": [ + "event fault" + ] + }, + { + "id": "X03-request-explicit-state", + "status": "pass", + "observed": { + "caught": [], + "globalErrors": [], + "requests": [ + 503 + ] + }, + "state": "request failed", + "browserErrors": [] + }, + { + "id": "X04-fallback-escapes-inner", + "status": "pass", + "observed": { + "caught": [ + { + "boundary": "outer", + "message": "fallback fault" + } + ], + "globalErrors": [], + "requests": [] + }, + "enclosingFallback": true, + "browserErrors": [] + }, + { + "id": "X05-focus-existing-invoker", + "status": "pass", + "activeId": "invoker", + "browserErrors": [] + }, + { + "id": "X06-focus-removed-default", + "status": "pass", + "activeTag": "BODY", + "workflowTargetSelected": false, + "browserErrors": [] + }, + { + "id": "X07-focus-removed-explicit", + "status": "pass", + "activeId": "next", + "applicationPolicy": true, + "browserErrors": [] + }, + { + "id": "X08-nested-return", + "status": "pass", + "innerReturn": "nested-invoker", + "outerReturn": "invoker", + "browserErrors": [] + } + ], + "passed": 8, + "total": 8 + }, + { + "date": "2026-09-26T19:58:16.005Z", + "evidence": "synthetic-browser-fixture-real-libraries", + "node": "v24.11.1", + "browser": "151.0.7922.34", + "browserSelection": "explicit executable override", + "packages": { + "react": "19.3.0", + "react-dom": "19.3.0", + "@radix-ui/react-dialog": "1.1.23", + "playwright": "1.63.0", + "esbuild": "0.28.2" + }, + "files": { + "fixture.jsx": "d01a78f533a8ac4c086fc93845ce5b86a5b676aee86a22ee53ac91ad2a77f936", + "experiment.mjs": "df9912cacd5b01b10fdc70a45d50b36d91202c44c0f57358334a1d3c41b4c88d", + "package.json": "a0ad37e8518b0c561d6b4a091ee51f5bca90e0ce329b2be7ba75347f6dc26ae7", + "package-lock.json": "ec4b544d441f89d9c512bc9931c1818473327105d5ef9047dc04d6a721e68463" + }, + "cases": [ + { + "id": "X01-render-contained", + "status": "pass", + "observed": { + "caught": [ + { + "boundary": "inner", + "message": "render fault" + } + ], + "globalErrors": [], + "requests": [] + }, + "siblingUsable": true, + "browserErrors": [] + }, + { + "id": "X02-event-not-contained", + "status": "pass", + "observed": { + "caught": [], + "globalErrors": [ + "Uncaught Error: event fault" + ], + "requests": [] + }, + "panelPresent": true, + "browserErrors": [ + "event fault" + ] + }, + { + "id": "X03-request-explicit-state", + "status": "pass", + "observed": { + "caught": [], + "globalErrors": [], + "requests": [ + 503 + ] + }, + "state": "request failed", + "browserErrors": [] + }, + { + "id": "X04-fallback-escapes-inner", + "status": "pass", + "observed": { + "caught": [ + { + "boundary": "outer", + "message": "fallback fault" + } + ], + "globalErrors": [], + "requests": [] + }, + "enclosingFallback": true, + "browserErrors": [] + }, + { + "id": "X05-focus-existing-invoker", + "status": "pass", + "activeId": "invoker", + "browserErrors": [] + }, + { + "id": "X06-focus-removed-default", + "status": "pass", + "activeTag": "BODY", + "workflowTargetSelected": false, + "browserErrors": [] + }, + { + "id": "X07-focus-removed-explicit", + "status": "pass", + "activeId": "next", + "applicationPolicy": true, + "browserErrors": [] + }, + { + "id": "X08-nested-return", + "status": "fail", + "error": "Expected focus nested-invoker; got {\"id\":\"invoker\",\"tag\":\"BUTTON\",\"dialogs\":0}: page.waitForFunction: Timeout 5000ms exceeded.", + "browserErrors": [] + } + ], + "passed": 7, + "total": 8 + } +] diff --git a/research/experiments/boundary-recovery/experiment.mjs b/research/experiments/boundary-recovery/experiment.mjs new file mode 100644 index 0000000..ad920cd --- /dev/null +++ b/research/experiments/boundary-recovery/experiment.mjs @@ -0,0 +1,124 @@ +import {build} from 'esbuild'; +import {chromium} from 'playwright'; +import {createServer} from 'node:http'; +import {once} from 'node:events'; +import {readFile, writeFile} from 'node:fs/promises'; +import {createHash} from 'node:crypto'; +import {fileURLToPath} from 'node:url'; +import assert from 'node:assert/strict'; + +const dir = fileURLToPath(new URL('.', import.meta.url)); +const bundle = await build({entryPoints: [dir + 'fixture.jsx'], bundle: true, write: false, + define: {'process.env.NODE_ENV': '"development"'}, format: 'iife'}); +const server = createServer((req, res) => { + if (req.url === '/bundle.js') { + res.writeHead(200, {'Content-Type': 'application/javascript'}); res.end(bundle.outputFiles[0].contents); + } else if (req.url === '/failure') { + res.writeHead(503, {'Content-Type': 'text/plain'}); res.end('synthetic unavailable'); + } else { + res.writeHead(200, {'Content-Type': 'text/html'}); + res.end('Boundary probe
'); + } +}); +server.listen(0, '127.0.0.1'); await once(server, 'listening'); +const url = `http://127.0.0.1:${server.address().port}`; +let browser; +const cases = []; +async function probe(id, query, action) { + const page = await browser.newPage(); + page.setDefaultTimeout(5000); + const browserErrors = []; + page.on('pageerror', e => browserErrors.push(e.message)); + try { + await page.goto(url + query); + await page.getByRole('button').first().waitFor(); + const observed = await action(page); + cases.push({id, status: 'pass', ...observed, browserErrors}); + } catch (error) { + cases.push({id, status: 'fail', error: error.message, observed: await page.evaluate(() => window.observed).catch(() => null), browserErrors}); + } finally {await page.close();} +} +async function active(page, id) { + try { await page.waitForFunction(id => document.activeElement?.id === id, id, {timeout: 5000}); } + catch (error) { throw new Error(`Expected focus ${id}; got ${await page.evaluate(() => ({id: document.activeElement?.id, tag: document.activeElement?.tagName, dialogs: document.querySelectorAll('[role=dialog]').length})).then(JSON.stringify)}: ${error.message}`); } +} +try { + browser = await chromium.launch({headless: true, + ...(process.env.FEP_CHROMIUM_PATH ? {executablePath: process.env.FEP_CHROMIUM_PATH} : {})}); + await probe('X01-render-contained', '/', async page => { + await page.getByText('Break render', {exact: true}).click(); + await page.getByTestId('inner').waitFor(); + await page.getByText('Sibling 0', {exact: true}).click(); + await page.getByText('Sibling 1', {exact: true}).waitFor(); + const o = await page.evaluate(() => window.observed); + assert(o.caught.some(x => x.boundary === 'inner' && x.message === 'render fault')); + assert.equal(await page.getByTestId('outer').count(), 0); + return {observed: o, siblingUsable: true}; + }); + await probe('X02-event-not-contained', '/', async page => { + await page.getByText('Break event', {exact: true}).click(); + await page.waitForFunction(() => window.observed.globalErrors.some(x => x.includes('event fault'))); + const o = await page.evaluate(() => window.observed); + assert.equal(o.caught.length, 0); assert.equal(await page.getByTestId('panel').count(), 1); + return {observed: o, panelPresent: true}; + }); + await probe('X03-request-explicit-state', '/', async page => { + await page.getByText('Fail request', {exact: true}).click(); + await page.getByRole('status').filter({hasText: 'request failed'}).waitFor(); + const o = await page.evaluate(() => window.observed); + assert.deepEqual(o.requests, [503]); assert.equal(o.caught.length, 0); + return {observed: o, state: 'request failed'}; + }); + await probe('X04-fallback-escapes-inner', '/?brokenFallback=1', async page => { + await page.getByText('Break render', {exact: true}).click(); + await page.getByTestId('outer').waitFor(); + const o = await page.evaluate(() => window.observed); + assert(o.caught.some(x => x.boundary === 'outer' && x.message === 'fallback fault')); + return {observed: o, enclosingFallback: true}; + }); + await probe('X05-focus-existing-invoker', '/?mode=focus', async page => { + await page.locator('#invoker').focus(); await page.keyboard.press('Enter'); + await active(page, 'remove'); await page.keyboard.press('Escape'); await active(page, 'invoker'); + return {activeId: await page.evaluate(() => document.activeElement.id)}; + }); + await probe('X06-focus-removed-default', '/?mode=focus', async page => { + await page.locator('#invoker').focus(); await page.keyboard.press('Enter'); await active(page, 'remove'); + await page.keyboard.press('Enter'); await page.getByRole('dialog').waitFor({state: 'detached'}); + await page.waitForFunction(() => document.activeElement === document.body); + // Negative control: default restoration cannot choose a workflow target for the removed trigger. + assert.equal(await page.locator('#invoker').count(), 0); + return {activeTag: await page.evaluate(() => document.activeElement.tagName), workflowTargetSelected: false}; + }); + await probe('X07-focus-removed-explicit', '/?mode=focus&explicit=1', async page => { + await page.locator('#invoker').focus(); await page.keyboard.press('Enter'); await active(page, 'remove'); + await page.keyboard.press('Enter'); await active(page, 'next'); + return {activeId: await page.evaluate(() => document.activeElement.id), applicationPolicy: true}; + }); + await probe('X08-nested-return', '/?mode=focus', async page => { + await page.locator('#invoker').focus(); await page.keyboard.press('Enter'); await active(page, 'remove'); + await page.keyboard.press('Tab'); await active(page, 'nested-invoker'); + await page.keyboard.press('Enter'); await active(page, 'nested-close'); + // Experimental timing variable, not a hidden retry: compare immediate versus settled Escape. + if (Number(process.env.FEP_NESTED_SETTLE_MS)) await page.waitForTimeout(Number(process.env.FEP_NESTED_SETTLE_MS)); + await page.keyboard.press('Escape'); await active(page, 'nested-invoker'); + assert.equal(await page.getByRole('dialog').count(), 1); + await page.keyboard.press('Escape'); await active(page, 'invoker'); + return {innerReturn: 'nested-invoker', outerReturn: 'invoker', observed: await page.evaluate(() => window.observed)}; + }); + const files = {}; + for (const name of ['fixture.jsx', 'experiment.mjs', 'package.json', 'package-lock.json']) { + files[name] = createHash('sha256').update(await readFile(dir + name)).digest('hex'); + } + const result = {date: new Date().toISOString(), evidence: 'synthetic-browser-fixture-real-libraries', + node: process.version, browser: browser.version(), nestedSettleMs: Number(process.env.FEP_NESTED_SETTLE_MS || 0), + browserSelection: process.env.FEP_CHROMIUM_PATH ? 'explicit executable override' : 'playwright bundled', + packages: JSON.parse(await readFile(dir + 'package.json')).dependencies, + files, cases, passed: cases.filter(x => x.status === 'pass').length, total: cases.length}; + const output = process.env.FEP_RESULT_PATH || '/tmp/fep-boundary-result.json'; + await writeFile(output, JSON.stringify(result, null, 2) + '\n'); + console.log(JSON.stringify({output, passed: result.passed, total: result.total, cases}, null, 2)); + if (result.passed !== result.total) process.exitCode = 1; +} finally { + if (browser) await browser.close(); + await new Promise(resolve => server.close(resolve)); +} diff --git a/research/experiments/boundary-recovery/fixture.jsx b/research/experiments/boundary-recovery/fixture.jsx new file mode 100644 index 0000000..fcf3091 --- /dev/null +++ b/research/experiments/boundary-recovery/fixture.jsx @@ -0,0 +1,81 @@ +import React, {useState, useRef} from 'react'; +import {createRoot} from 'react-dom/client'; +import * as Dialog from '@radix-ui/react-dialog'; + +window.observed = {caught: [], globalErrors: [], requests: [], dialogTransitions: []}; +window.addEventListener('error', e => window.observed.globalErrors.push(e.message)); +class Boundary extends React.Component { + state = {failed: false}; + static getDerivedStateFromError() { return {failed: true}; } + componentDidCatch(error) { window.observed.caught.push({boundary: this.props.name, message: error.message}); } + render() { + if (this.state.failed) return this.props.brokenFallback + ? :

Region unavailable

; + return this.props.children; + } +} +function BrokenFallback() { throw new Error('fallback fault'); } +function Panel() { + const [broken, setBroken] = useState(false); + const [request, setRequest] = useState('idle'); + if (broken) throw new Error('render fault'); + return
+ + + +

{request}

+
; +} +function FailureFixture() { + const [clicks, setClicks] = useState(0); + const brokenFallback = new URLSearchParams(location.search).has('brokenFallback'); + return
+ + + + +
; +} +function Modal({children, title}) { + return + + {title}{children} + + ; +} +function FocusFixture() { + const [open, setOpen] = useState(false); + const [present, setPresent] = useState(true); + const next = useRef(null); + const explicit = new URLSearchParams(location.search).has('explicit'); + return
+ + {window.observed.dialogTransitions.push({dialog: "outer", open: value}); setOpen(value);}}> + {present && Open task} + + { + if (explicit && !present) {event.preventDefault(); next.current?.focus();} + }}> + Record task + + window.observed.dialogTransitions.push({dialog: "inner", open: value})}> + Nested task + Close nested + + Close task + + + +
; +} +createRoot(document.getElementById('root')).render( + new URLSearchParams(location.search).get('mode') === 'focus' ? : +); diff --git a/research/experiments/boundary-recovery/package-lock.json b/research/experiments/boundary-recovery/package-lock.json new file mode 100644 index 0000000..187c03b --- /dev/null +++ b/research/experiments/boundary-recovery/package-lock.json @@ -0,0 +1,989 @@ +{ + "name": "fep-boundary-recovery", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "fep-boundary-recovery", + "dependencies": { + "@radix-ui/react-dialog": "1.1.23", + "esbuild": "0.28.2", + "playwright": "1.63.0", + "react": "19.3.0", + "react-dom": "19.3.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.7.tgz", + "integrity": "sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.5.tgz", + "integrity": "sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.2.tgz", + "integrity": "sha512-RHCUGwKHDr0hDGg4X7ma4JG4/+12qxw8rkh5QKdDldlCvtja6nUx1Ef/8HVrJze81lEsgLQlqjzjGNHantgnQA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.23.tgz", + "integrity": "sha512-Ksw4WeROkO4rC9k/onilX/Ao2Cr1ku1unMNH+XSCcP4jSXYu7HDsg9n4ojMjVb22XpYjAQ9qfrFlVbru1vXDUA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.19.tgz", + "integrity": "sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-effect-event": "0.0.5" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.6.tgz", + "integrity": "sha512-RNOJjfZMTyBM6xYmV3IVGXkPjIhcBAuv48POevAXwrGJhkWZ9p1rFoIS1JFooPuT193AZmRsCPhpoVJxx6OPoQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.16.tgz", + "integrity": "sha512-wmRZ2WWLvmt6KHy2rNPOdPUjwq5xOHY02+m+udwJTn0aNIox/rkskAvJTyTLGhPK6KgrUjlJUJpgmx/+wFiFIQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.4.tgz", + "integrity": "sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.17.tgz", + "integrity": "sha512-vKQLcWypUnwZVvfV7UkGahH2g6ySe8M8R+zYBwPrv5byZ9QAW6cQVvNKo7GgmD+p8aYb6D9JBuvy8/WhOno2wQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.10.tgz", + "integrity": "sha512-3wyzCQ6+ubRA+D4uv9m95JYLXxmOHp05qjrkjeA7uKHHtjpPggQzc6DAb0URl7j67oR0K2foO4ip27TiX037Bw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.10.tgz", + "integrity": "sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.3.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.3.tgz", + "integrity": "sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.5" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.4.tgz", + "integrity": "sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.6.tgz", + "integrity": "sha512-uEQJGT97ZA/TgP/Hydw47lHu+/vQj6z/0jA+WeTbK1o9Rx45GImjpD0tc3W5ad3D6XTSR6e1yEO0FvGq6WQfVQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-use-effect-event": "0.0.5", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.5.tgz", + "integrity": "sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.4.tgz", + "integrity": "sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/playwright": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.63.0.tgz", + "integrity": "sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==", + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.63.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/playwright-core": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.63.0.tgz", + "integrity": "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==", + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/react": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.3.0.tgz", + "integrity": "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.0.tgz", + "integrity": "sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.28.0" + }, + "peerDependencies": { + "react": "^19.3.0" + } + }, + "node_modules/react-remove-scroll": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/scheduler": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz", + "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + } + } +} diff --git a/research/experiments/boundary-recovery/package.json b/research/experiments/boundary-recovery/package.json new file mode 100644 index 0000000..a92aa69 --- /dev/null +++ b/research/experiments/boundary-recovery/package.json @@ -0,0 +1,13 @@ +{ + "name": "fep-boundary-recovery", + "private": true, + "type": "module", + "scripts": {"test": "node experiment.mjs"}, + "dependencies": { + "react": "19.3.0", + "react-dom": "19.3.0", + "@radix-ui/react-dialog": "1.1.23", + "playwright": "1.63.0", + "esbuild": "0.28.2" + } +} diff --git a/research/experiments/boundary-recovery/provenance.json b/research/experiments/boundary-recovery/provenance.json new file mode 100644 index 0000000..d695934 --- /dev/null +++ b/research/experiments/boundary-recovery/provenance.json @@ -0,0 +1,57 @@ +{ + "date": "2026-09-26", + "source_id": "SRC-0029", + "packages": { + "node_modules/react": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.3.0.tgz", + "integrity": "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==" + }, + "node_modules/react-dom": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.0.tgz", + "integrity": "sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==" + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.23.tgz", + "integrity": "sha512-Ksw4WeROkO4rC9k/onilX/Ao2Cr1ku1unMNH+XSCcP4jSXYu7HDsg9n4ojMjVb22XpYjAQ9qfrFlVbru1vXDUA==" + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.16.tgz", + "integrity": "sha512-wmRZ2WWLvmt6KHy2rNPOdPUjwq5xOHY02+m+udwJTn0aNIox/rkskAvJTyTLGhPK6KgrUjlJUJpgmx/+wFiFIQ==" + }, + "node_modules/playwright": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.63.0.tgz", + "integrity": "sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==" + }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==" + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.19.tgz", + "integrity": "sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w==" + } + }, + "inspected_files": { + "@radix-ui/react-dialog/dist/index.mjs": { + "sha256": "a37daaef3f67c502500404ee93f60a04fdf05d2b0ce4783c858b1027c7f54792" + }, + "@radix-ui/react-focus-scope/dist/index.mjs": { + "sha256": "798184fbe00268a6c4a539fd2073b85b6dfef6d89bd2cd547d9a66ae1024e55d" + }, + "@radix-ui/react-dismissable-layer/dist/index.mjs": { + "sha256": "01df449a23d65bab1f2f2215a953cba74741419496ebd9a7932533f267cc873d" + } + }, + "browser": { + "version": "151.0.7922.34", + "executable_sha256": "0b20b130e7edd9dd51873be867761295fe0cfad490c2b9a64f95bd3cfc08fa71", + "selection": "preinstalled Chromium build 1234, explicit executable override; not claimed to be Playwright 1.63 bundled revision" + } +} diff --git a/research/experiments/boundary-recovery/result-immediate.json b/research/experiments/boundary-recovery/result-immediate.json new file mode 100644 index 0000000..3f467f6 --- /dev/null +++ b/research/experiments/boundary-recovery/result-immediate.json @@ -0,0 +1,134 @@ +{ + "date": "2026-09-26T19:58:51.268Z", + "evidence": "synthetic-browser-fixture-real-libraries", + "node": "v24.11.1", + "browser": "151.0.7922.34", + "nestedSettleMs": 0, + "browserSelection": "explicit executable override", + "packages": { + "react": "19.3.0", + "react-dom": "19.3.0", + "@radix-ui/react-dialog": "1.1.23", + "playwright": "1.63.0", + "esbuild": "0.28.2" + }, + "files": { + "fixture.jsx": "3dd538006affc9bcdb69c8b5cd1bb06ac335499f1143b91f06f1d455a127c525", + "experiment.mjs": "33ea08bcbd8fda533dc4bb44d5369b5453fd8e23940116597658435296d834fb", + "package.json": "a0ad37e8518b0c561d6b4a091ee51f5bca90e0ce329b2be7ba75347f6dc26ae7", + "package-lock.json": "ec4b544d441f89d9c512bc9931c1818473327105d5ef9047dc04d6a721e68463" + }, + "cases": [ + { + "id": "X01-render-contained", + "status": "pass", + "observed": { + "caught": [ + { + "boundary": "inner", + "message": "render fault" + } + ], + "globalErrors": [], + "requests": [], + "dialogTransitions": [] + }, + "siblingUsable": true, + "browserErrors": [] + }, + { + "id": "X02-event-not-contained", + "status": "pass", + "observed": { + "caught": [], + "globalErrors": [ + "Uncaught Error: event fault" + ], + "requests": [], + "dialogTransitions": [] + }, + "panelPresent": true, + "browserErrors": [ + "event fault" + ] + }, + { + "id": "X03-request-explicit-state", + "status": "pass", + "observed": { + "caught": [], + "globalErrors": [], + "requests": [ + 503 + ], + "dialogTransitions": [] + }, + "state": "request failed", + "browserErrors": [] + }, + { + "id": "X04-fallback-escapes-inner", + "status": "pass", + "observed": { + "caught": [ + { + "boundary": "outer", + "message": "fallback fault" + } + ], + "globalErrors": [], + "requests": [], + "dialogTransitions": [] + }, + "enclosingFallback": true, + "browserErrors": [] + }, + { + "id": "X05-focus-existing-invoker", + "status": "pass", + "activeId": "invoker", + "browserErrors": [] + }, + { + "id": "X06-focus-removed-default", + "status": "pass", + "activeTag": "BODY", + "workflowTargetSelected": false, + "browserErrors": [] + }, + { + "id": "X07-focus-removed-explicit", + "status": "pass", + "activeId": "next", + "applicationPolicy": true, + "browserErrors": [] + }, + { + "id": "X08-nested-return", + "status": "fail", + "error": "Expected focus nested-invoker; got {\"id\":\"invoker\",\"tag\":\"BUTTON\",\"dialogs\":0}: page.waitForFunction: Timeout 5000ms exceeded.", + "observed": { + "caught": [], + "globalErrors": [], + "requests": [], + "dialogTransitions": [ + { + "dialog": "outer", + "open": true + }, + { + "dialog": "inner", + "open": true + }, + { + "dialog": "outer", + "open": false + } + ] + }, + "browserErrors": [] + } + ], + "passed": 7, + "total": 8 +} diff --git a/research/experiments/boundary-recovery/result-settled.json b/research/experiments/boundary-recovery/result-settled.json new file mode 100644 index 0000000..5d25346 --- /dev/null +++ b/research/experiments/boundary-recovery/result-settled.json @@ -0,0 +1,139 @@ +{ + "date": "2026-09-26T19:58:59.954Z", + "evidence": "synthetic-browser-fixture-real-libraries", + "node": "v24.11.1", + "browser": "151.0.7922.34", + "nestedSettleMs": 100, + "browserSelection": "explicit executable override", + "packages": { + "react": "19.3.0", + "react-dom": "19.3.0", + "@radix-ui/react-dialog": "1.1.23", + "playwright": "1.63.0", + "esbuild": "0.28.2" + }, + "files": { + "fixture.jsx": "3dd538006affc9bcdb69c8b5cd1bb06ac335499f1143b91f06f1d455a127c525", + "experiment.mjs": "33ea08bcbd8fda533dc4bb44d5369b5453fd8e23940116597658435296d834fb", + "package.json": "a0ad37e8518b0c561d6b4a091ee51f5bca90e0ce329b2be7ba75347f6dc26ae7", + "package-lock.json": "ec4b544d441f89d9c512bc9931c1818473327105d5ef9047dc04d6a721e68463" + }, + "cases": [ + { + "id": "X01-render-contained", + "status": "pass", + "observed": { + "caught": [ + { + "boundary": "inner", + "message": "render fault" + } + ], + "globalErrors": [], + "requests": [], + "dialogTransitions": [] + }, + "siblingUsable": true, + "browserErrors": [] + }, + { + "id": "X02-event-not-contained", + "status": "pass", + "observed": { + "caught": [], + "globalErrors": [ + "Uncaught Error: event fault" + ], + "requests": [], + "dialogTransitions": [] + }, + "panelPresent": true, + "browserErrors": [ + "event fault" + ] + }, + { + "id": "X03-request-explicit-state", + "status": "pass", + "observed": { + "caught": [], + "globalErrors": [], + "requests": [ + 503 + ], + "dialogTransitions": [] + }, + "state": "request failed", + "browserErrors": [] + }, + { + "id": "X04-fallback-escapes-inner", + "status": "pass", + "observed": { + "caught": [ + { + "boundary": "outer", + "message": "fallback fault" + } + ], + "globalErrors": [], + "requests": [], + "dialogTransitions": [] + }, + "enclosingFallback": true, + "browserErrors": [] + }, + { + "id": "X05-focus-existing-invoker", + "status": "pass", + "activeId": "invoker", + "browserErrors": [] + }, + { + "id": "X06-focus-removed-default", + "status": "pass", + "activeTag": "BODY", + "workflowTargetSelected": false, + "browserErrors": [] + }, + { + "id": "X07-focus-removed-explicit", + "status": "pass", + "activeId": "next", + "applicationPolicy": true, + "browserErrors": [] + }, + { + "id": "X08-nested-return", + "status": "pass", + "innerReturn": "nested-invoker", + "outerReturn": "invoker", + "observed": { + "caught": [], + "globalErrors": [], + "requests": [], + "dialogTransitions": [ + { + "dialog": "outer", + "open": true + }, + { + "dialog": "inner", + "open": true + }, + { + "dialog": "inner", + "open": false + }, + { + "dialog": "outer", + "open": false + } + ] + }, + "browserErrors": [] + } + ], + "passed": 8, + "total": 8 +} diff --git a/research/reviews/BoundaryRecoveryExperiment.md b/research/reviews/BoundaryRecoveryExperiment.md new file mode 100644 index 0000000..cc72055 --- /dev/null +++ b/research/reviews/BoundaryRecoveryExperiment.md @@ -0,0 +1,60 @@ +# Boundary recovery observations — 2026-09-26 + +Status: executed synthetic browser fixture, editorial interpretation by Codex. +WP-0003 T01; SRC-0029 pinned packages and SRC-0030 Radix documentation. This advances +implementation evidence for Failure Boundary and Focus Return, not full-pattern +recurrence, application validation or the M1/M2 gates. Sixteen hypotheses remain. + +[Runner, fixture and raw results](../experiments/boundary-recovery/README.md) use +React 19.3.0, Radix Dialog 1.1.23 and Chromium 151.0.7922.34. Package archives, +inspected implementation hashes and browser provenance are recorded. Sources of +injected faults and application focus policy are our own fixture, not vendor examples. +No live model calls; future inference remains through llm-connect. + +## Observations and implications + +| Evidence | Executed observation | Bounded interpretation | +| --- | --- | --- | +| X01 | Inner boundary catches render fault; sibling button remains usable | Local rendering containment works for this subtree and fault | +| X02 | Event-handler exception is globally reported; no boundary catch | Do not promise catch-all UI recovery from a rendering boundary | +| X03 | GET /failure returns 503; explicit application catch displays failure state | Request state is separate; this does not establish behavior of unhandled async errors | +| X04 | Broken inner fallback reaches outer fallback | Recovery UI can fail and needs an enclosing policy | +| X05 | Escape returns focus to surviving trigger | A partial implementation of ordinary modal return | +| X06 | Removing the trigger and closing leaves BODY focused | A generic trigger reference cannot choose the next workflow target | +| X07 | Application onCloseAutoFocus override selects next record | A narrow application policy repairs this removed-trigger case | +| X08 | Immediate Escape after nested activation closed outer dialog in the recorded final immediate run; 100 ms pause preserved inner-then-outer return | Activation/focus arrival alone is insufficient evidence of stable dismissal ownership; timing cause remains unproven | + +## Source inspection and uncertainty + +[Radix Dialog docs](https://www.radix-ui.com/primitives/docs/components/dialog) +expose close-autofocus customization and describe Escape dismissal. Inspected package +code `@radix-ui/react-dialog/dist/index.mjs` lines 154–156 cancels the default unmount +focus action and attempts focus on the trigger reference. Our explicit target in X07 +is an application addition, not a built-in workflow-selection guarantee. + +In the pinned `@radix-ui/react-focus-scope/dist/index.mjs`, lines 94–103 defer unmount +focus handling. The pinned dismissable-layer module, lines 90–120, selects the highest +layer and registers Escape listeners/layers through effects. These are plausible +places to investigate the timing discrepancy, not a demonstrated causal diagnosis. +See [file hashes and transitive versions](../experiments/boundary-recovery/provenance.json). + +The failing X08 transition log is outer-open, inner-open, outer-close; it does not +show inner-close before outer-close. The final settled run shows the intended order. +Three earlier development runs also alternated fail/pass/fail. Do not describe this +as an unconditional nested-focus guarantee, estimate a rate from five exploratory +runs, or prescribe a 100 ms sleep as a fix. Current result files record identical +fixture/runner/lock hashes, with the timing parameter explicitly different. + +## Candidate revisions and remaining work + +[Failure Boundary](../candidates/FailureBoundary.md) gains executed partial support +and concrete exclusions. [Focus Return](../candidates/FocusReturn.md) gains a negative +control, a conditional recovery policy and an unresolved nested-lifecycle case. +Both stay hypotheses. One synthetic fixture is not an independent application use. + +Next under [WP-0003 T01](../../workplans/FEP-WP-0003-contrasting-schools.md): isolate +nested dismissal with event/effect timing instrumentation and compare production +build plus another browser before assigning cause. Preserve the immediate path as +a regression probe; test explicit readiness/ownership rather than adding a delay. +Screen-reader behavior remains untested. Feature/release coupling inspection and +navigation/history/cache checks remain separate pending work. diff --git a/research/reviews/NavigationCoverage.md b/research/reviews/NavigationCoverage.md index 70d7344..51efbfe 100644 --- a/research/reviews/NavigationCoverage.md +++ b/research/reviews/NavigationCoverage.md @@ -16,3 +16,7 @@ were added in the breadth review. Implementation evidence for B/C remains pendin All six streams now have some documentary material; none is thereby complete. VC-01/02 are synthetic reviewed drafts; VC-03 is provisional; VC-04–10 remain planned. No source count is used as a recurrence score or evidence-gate substitute. + +Boundary follow-up: [React/Radix browser observations](BoundaryRecoveryExperiment.md) +add bounded executable evidence to F. Nested dismissal remains timing-sensitive; +no screen-reader verification or full quality-stream completion. diff --git a/workplans/FEP-WP-0003-contrasting-schools.md b/workplans/FEP-WP-0003-contrasting-schools.md index 09c8f9d..1d9c210 100644 --- a/workplans/FEP-WP-0003-contrasting-schools.md +++ b/workplans/FEP-WP-0003-contrasting-schools.md @@ -81,3 +81,19 @@ pin an application and inspect feature imports versus release coupling; exercise render versus request failures and modal focus after invoker removal. Previous navigation/history/cache checks remain open. WP-0002 retains independent-human validation. No frozen-pilot edits, canonical promotion or evidence-gate completion. + +## Executed rendering/focus boundary slice — 2026-09-26 + +T01 progress: research/experiments/boundary-recovery pins React 19.3.0, Radix Dialog +1.1.23 and all dependencies; Chromium fixture exercises eight cases. Final immediate +run meets 7/8 assertions; separate 100 ms nested-settling condition meets 8/8. Three +earlier development results retained, including failures. Ordinary render containment, +event-handler exclusion, explicit 503 state, fallback escalation, existing/removed +trigger focus and explicit replacement target observed. Immediate nested Escape can +close the outer task; root cause unresolved. No delay prescribed as a fix. + +Next T01 action: isolate nested dismissal timing, compare production and another +browser, verify readiness/ownership without arbitrary delay. Do not mark its assertion +resolved from the settled run. Feature/release inspection and navigation checks remain +pending. T02/T03 unchanged, sixteen hypotheses, no independent human validation or +M1/M2 completion. See research/reviews/BoundaryRecoveryExperiment.md; no model calls.