Test competing navigation ownership and review research thread

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a0cffe-206f-7323-89d3-b20ca4cf4bf9
This commit is contained in:
tegwick 2026-09-27 00:45:54 +02:00
parent e484cae45f
commit e8ccb73a12
22 changed files with 4981 additions and 13 deletions

View file

@ -353,6 +353,8 @@ research/evolution/context.json
research/evolution/cycles/2026-09-27-navigation-effort.json
research/evolution/cycles/2026-09-27-navigation-focus-effort.json
research/evolution/cycles/2026-09-27-navigation-focus.md
research/evolution/cycles/2026-09-27-navigation-ownership-effort.json
research/evolution/cycles/2026-09-27-navigation-ownership.md
research/evolution/cycles/2026-09-27-navigation.md
research/evolution/ledger.json
research/evolution/profiles.json
@ -407,6 +409,11 @@ research/experiments/navigation-history/focus-policy.js
research/experiments/navigation-history/focus-result.json
research/experiments/navigation-history/focus.mjs
research/experiments/navigation-history/initial-partial.json
research/experiments/navigation-history/ownership-initial-result.json
research/experiments/navigation-history/ownership-initial.mjs
research/experiments/navigation-history/ownership-policy.js
research/experiments/navigation-history/ownership-result.json
research/experiments/navigation-history/ownership.mjs
research/experiments/navigation-history/package-lock.json
research/experiments/navigation-history/package.json
research/experiments/navigation-history/result.json
@ -431,7 +438,9 @@ research/reviews/MatrixRecoveryExperiment.md
research/reviews/NavigationCoverage.md
research/reviews/NavigationFocusExperiment.md
research/reviews/NavigationHistoryExperiment.md
research/reviews/NavigationOwnershipExperiment.md
research/reviews/NavigationRenderingEvidence.md
research/reviews/NavigationThreadReview.md
research/reviews/NestedDismissalIsolation.md
research/reviews/NestedOwnershipRouting.md
research/reviews/ObsidianConflictScreen.json

View file

@ -85,3 +85,12 @@ this fixture restores a same-ID link; that behavior alone does not choose the ne
task. Preserve this candidate's modal scope instead of treating every route change
as a return-to-invoker problem. No general route-focus pattern or AT validation is
admitted; delayed history restoration and competing navigation remain open.
## Completion ownership contrast — 2026-09-27
[NO01–NO06](../reviews/NavigationOwnershipExperiment.md) strengthen the route/modal
scope boundary. A still-current history result can restore content without taking
focus from a newer search task; an obsolete navigation result must not supply a
continuation at all. The earlier single-response route policy is insufficient for
competing completions. This contrast does not expand the modal candidate into a
universal routing pattern or prove AT behavior.

View file

@ -65,3 +65,18 @@ return to the invoker. Check ownership when the response is applied, not only wh
it is requested. Failure feedback need not move focus. Same-ID restoration is not
proof of semantic continuity. DOM/ARIA checks here do not establish spoken feedback;
AT, delayed history and overlapping-request behavior remain unverified.
## Separate completion and focus ownership — 2026-09-27
[Competing navigation](../reviews/NavigationOwnershipExperiment.md) NO01–NO06 exposes
limits of the preceding single-response policy. Validate ownership before any result
effects, including old errors and history restoration, not only before moving focus.
A current response may update content/title/status while the user has moved focus
to another surviving task; an obsolete response may own none of those effects.
The latest-intent policy tested here does not revive older success when newer intent
fails. Ignoring completion does not abort or undo server work.
A scoped navigation generation and separate focus-change check meet the tested
obligations. Their history adapter depends on pinned htmx internals; multiple history
misses, nonzero swap delays, redirects, lifecycle teardown and AT validation remain
open. Do not apply this navigation policy indiscriminately to unrelated background work.

View file

@ -47,3 +47,19 @@ a fragment served as a full document in Chromium and Firefox; correct selection
keeps representations distinct, without promising cache retention or freshness.
See [NH01–NH06](../reviews/NavigationHistoryExperiment.md). One authored application
with a real package, not independent deployed recurrence. Hypothesis retained.
## Competing completion ownership — 2026-09-27
[NO01–NO06](../reviews/NavigationOwnershipExperiment.md) show a delayed history response
replacing content/title while a newer URL remains selected in the unguarded fixture.
Treat navigation initiation, including Back/Forward, as intent with an explicit
completion owner. Before applying an asynchronous result, check that it still owns
the selected route; include URL, content, title and feedback in that decision.
A response to obsolete intent is not made current by arriving last or succeeding.
The experiment's latest-intent policy keeps the last committed view and newest error
when a newer request fails; it does not revive older pending navigation implicitly.
Other policies require an explicit contract. Browser history can change the URL before
loading completes: pending-state disclosure/recovery is still unresolved here.
The authored guard supports this obligation only in the tested schedules, not a
portable router or proof of independent application recurrence.

View file

@ -189,3 +189,10 @@ order and programmatically exposed feedback; no conformance claim from this fixt
SRC-0040: [htmx events](https://htmx.org/events/), lifecycle-hook reference accessed
2026-09-27, living revision unknown. Same htmx family as SRC-0018/0019 and SRC-0038.
Runtime conclusions use pinned 2.0.11 code and hashed receipts.
SRC-0038 ownership follow-up, 2026-09-27: [NO01–NO06](../reviews/NavigationOwnershipExperiment.md)
compares the unchanged prior focus policy with an authored generation/focus guard.
24 completed rows, guard 12/12 full obligations versus baseline 0/12, with initial
keyboard-helper failures retained. Same fixture family and pinned htmx package.
SRC-0040 event documentation and pinned loadHistoryFromServer source delimit the
experimental history-XHR adapter; no vendor patch or production claim.

View file

@ -55,9 +55,9 @@ Ten selected retrospective outcomes (not a historical census), followed by prosp
| inconclusive | 0 |
| merge | 0 |
| rejection | 0 |
| revision | 10 |
| revision | 11 |
Cycles with recorded research effort: 2/12. Unknown effort is neither zero nor estimated from commits, tokens or assertion counts.
Cycles with recorded research effort: 3/13. Unknown effort is neither zero nor estimated from commits, tokens or assertion counts.
### Stream B / B-navigation-mechanism-v1
@ -75,6 +75,14 @@ One bounded question counts at most one consequential outcome. Corroboration and
One bounded question counts at most one consequential outcome. Corroboration and inconclusive work remain visible separately. Fewer than three comparable cycles: no trend assessment.
### Stream F / F-navigation-ownership-v1
| Cycle | Outcome | Research minutes | Consequential outcome/hour |
| --- | --- | ---: | ---: |
| LEARN-0013 | revision | 13.918 | 4.31 |
One bounded question counts at most one consequential outcome. Corroboration and inconclusive work remain visible separately. Fewer than three comparable cycles: no trend assessment.
## Coherence and maintainability
Profile hashes and local references passed validation when this report was generated. That checks traceability, not source truth or external URL freshness.
@ -222,8 +230,9 @@ Make a navigable state reconstructible from its URL.
| documentary | htmx | History behavior and history-cache misses. | [NavigationRenderingEvidence](../../research/reviews/NavigationRenderingEvidence.md) |
| documentary | Web platform | pushState changes history, not server dereferenceability. | [NavigationRenderingEvidence](../../research/reviews/NavigationRenderingEvidence.md) |
| runtime | htmx in authored public-catalog fixture | NH01–NH06: native/enhanced routes, history hit/miss, actual browser cache ordering and missing-Vary negative control; explicit Node validator checks separate. | [NavigationHistoryExperiment](../../research/reviews/NavigationHistoryExperiment.md) |
| runtime | htmx in authored public-catalog fixture | NO01–NO06: ordered history/success/error races, prior policy 0/12 full obligations versus guarded 12/12; separate focus ownership. Same fixture family; pinned history-XHR adapter. | [NavigationOwnershipExperiment](../../research/reviews/NavigationOwnershipExperiment.md) |
Open limits: Independent deployed full-contract recurrence remains unverified. Keyboard/focus/announcements, late-script activation, interrupted transport, invalid/deleted routes, offline availability and real intermediary behavior remain open.
Open limits: Independent application recurrence and human/AT validation remain unverified. Pending history disclosure, multiple history misses, redirects, nonzero swap delays, bfcache, lifecycle cleanup, interrupted/late enhancement and offline availability remain open.
Superseded by: none recorded.
@ -239,8 +248,9 @@ Keep essential actions usable when enhancement is unavailable.
| documentary | SvelteKit | Form actions and rendering/page options. | [NavigationRenderingEvidence](../../research/reviews/NavigationRenderingEvidence.md) |
| runtime | htmx in authored public-catalog fixture | NH01–NH06: native/enhanced routes, history hit/miss, actual browser cache ordering and missing-Vary negative control; explicit Node validator checks separate. | [NavigationHistoryExperiment](../../research/reviews/NavigationHistoryExperiment.md) |
| runtime | htmx in authored public-catalog fixture | NF01–NF06: Tab/Enter navigation, raw versus authored focus/status policy, held-response ownership and background update; DOM evidence only, not AT speech. | [NavigationFocusExperiment](../../research/reviews/NavigationFocusExperiment.md) |
| runtime | htmx in authored public-catalog fixture | NO01–NO06: ordered history/success/error races, prior policy 0/12 full obligations versus guarded 12/12; separate focus ownership. Same fixture family; pinned history-XHR adapter. | [NavigationOwnershipExperiment](../../research/reviews/NavigationOwnershipExperiment.md) |
Open limits: Independent deployed full-contract recurrence and human/AT validation remain unverified. Focus visibility, delayed history, overlapping requests, late-script activation, interrupted transport, invalid/deleted routes, offline availability and real intermediaries remain open.
Open limits: Independent application recurrence and human/AT validation remain unverified. Pending history disclosure, multiple history misses, redirects, nonzero swap delays, bfcache, lifecycle cleanup, interrupted/late enhancement and offline availability remain open.
Superseded by: none recorded.
@ -328,8 +338,9 @@ Restore meaningful focus after a temporary interaction ends.
| static-code | Radix | Pinned layer/listener ownership implementation. | [NestedDismissalIsolation](../../research/reviews/NestedDismissalIsolation.md) |
| runtime | Radix | Authored fixture: raw 20/24 versus routed 24/24; browser/build limits retained. | [NestedOwnershipRouting](../../research/reviews/NestedOwnershipRouting.md) |
| runtime | htmx in authored public-catalog fixture | NF01–NF06: Tab/Enter navigation, raw versus authored focus/status policy, held-response ownership and background update; DOM evidence only, not AT speech. | [NavigationFocusExperiment](../../research/reviews/NavigationFocusExperiment.md) |
| runtime | htmx in authored public-catalog fixture | NO01–NO06: ordered history/success/error races, prior policy 0/12 full obligations versus guarded 12/12; separate focus ownership. Same fixture family; pinned history-XHR adapter. | [NavigationOwnershipExperiment](../../research/reviews/NavigationOwnershipExperiment.md) |
Open limits: One implementation family; repeated trials are not independent uses. Animated exits and competing owners remain untested; no production repair claim. Route continuation is a contrasting context, not independent modal-pattern recurrence; delayed history and AT speech unverified.
Open limits: One implementation family; repeated trials are not independent uses. Animated exits and competing owners remain untested; no production repair claim. Route ownership is a contrast, not independent modal recurrence; AT speech and broader history/lifecycle cases remain unverified.
Superseded by: none recorded.
@ -519,3 +530,17 @@ Evidence: [NavigationFocusExperiment](../../research/reviews/NavigationFocusExpe
Remaining uncertainty: Author-model DOM evidence, no human/AT validation or independent recurrence. Delayed history focus, competing requests, delayed scripts and interrupted enhancement remain open.
Supersedes: none recorded.
### LEARN-0013 — Can obsolete history/navigation completions affect newer intent, and does separating completion ownership from focus ownership prevent it?
2026-09-27 · stream F · prospective · revision
Before: The single-response continuation policy preserved newer search focus but left competing history/success/error completions unresolved; focus-at-swap alone might not be sufficient.
Conclusion: Revise operational guidance: validate navigation ownership before content/URL/title/feedback effects, including stale errors and newer failure, independently of focus permission. Prior policy misses obligations in 12/12 adversarial rows; authored guard meets 12/12. Pause fixture work for independent source contrast.
Evidence: [NavigationOwnershipExperiment](../../research/reviews/NavigationOwnershipExperiment.md), [NavigationThreadReview](../../research/reviews/NavigationThreadReview.md), [ownership-result](../../research/experiments/navigation-history/ownership-result.json), [2026-09-27-navigation-ownership-effort](../../research/evolution/cycles/2026-09-27-navigation-ownership-effort.json).
Remaining uncertainty: One authored htmx family and author-model review, not production recurrence or AT validation. History adapter is pinned implementation-dependent; pending-state, multiple-miss and lifecycle limits remain open. Three distinct protocols are not a comparable diminishing-returns window.
Supersedes: none recorded.

View file

@ -0,0 +1,42 @@
{
"start": 199629.983211976,
"execution_seconds": 74.26050614900305,
"executions": [
{
"command": [
"node",
"research/experiments/navigation-history/ownership.mjs"
],
"seconds": 23.363541921018623,
"exit_code": 1
},
{
"command": [
"node",
"research/experiments/navigation-history/ownership.mjs"
],
"seconds": 37.19569087898708,
"exit_code": 0
},
{
"command": [
"make",
"evolution-check",
"evolution-test",
"schema-test",
"bench-test"
],
"seconds": 13.701273348997347,
"exit_code": 0
}
],
"status": "complete",
"end_utc": "2026-09-26T22:45:32.069777+00:00",
"start_utc_approx": "2026-09-26T22:30:22.733700+00:00",
"elapsed_seconds": 909.336,
"research_minutes": 13.918,
"execution_minutes": 1.238,
"method": "Monotonic single-agent session minus timed experiment/check subprocess intervals. Initial harness failures and corrected run both retained in execution. Checkpoint inspection assigned to execution, not double-counted.",
"excluded": "Orientation before start and final receipt refresh, commits, snapshot, decision/progress logging and sync after cutoff; unknown model tokens/cost.",
"budget": "Within registered 45 active / 10 execution minute budget; no scope extension."
}

View file

@ -410,6 +410,46 @@
"cost_usd": null
},
"supersedes": []
},
{
"id": "LEARN-0013",
"date": "2026-09-27",
"recording": "prospective",
"stream": "F",
"comparison_group": "F-navigation-ownership-v1",
"protocol_ref": "research/evolution/cycles/2026-09-27-navigation-ownership.md",
"question": "Can obsolete history/navigation completions affect newer intent, and does separating completion ownership from focus ownership prevent it?",
"previous_belief": "The single-response continuation policy preserved newer search focus but left competing history/success/error completions unresolved; focus-at-swap alone might not be sufficient.",
"conclusion": "Revise operational guidance: validate navigation ownership before content/URL/title/feedback effects, including stale errors and newer failure, independently of focus permission. Prior policy misses obligations in 12/12 adversarial rows; authored guard meets 12/12. Pause fixture work for independent source contrast.",
"disposition": "revision",
"affected_patterns": [
"CAND-0009",
"CAND-0010",
"CAND-0016"
],
"evidence": [
"research/reviews/NavigationOwnershipExperiment.md",
"research/reviews/NavigationThreadReview.md",
"research/experiments/navigation-history/ownership-result.json",
"research/evolution/cycles/2026-09-27-navigation-ownership-effort.json"
],
"remaining_uncertainty": [
"One authored htmx family and author-model review, not production recurrence or AT validation.",
"History adapter is pinned implementation-dependent; pending-state, multiple-miss and lifecycle limits remain open.",
"Three distinct protocols are not a comparable diminishing-returns window."
],
"reviewer": {
"name": "Codex",
"role": "author-model",
"independent_human": false
},
"effort": {
"research_minutes": 13.918,
"execution_minutes": 1.238,
"reported_tokens": null,
"cost_usd": null
},
"supersedes": []
}
]
}

View file

@ -317,7 +317,7 @@
"id": "CAND-0009",
"path": "research/candidates/URLAddressableState.md",
"title": "URL-addressable State",
"sha256": "3edc087c5b4b0a332dd9097334022cdb138437413b933f271e5cf96096cc63be",
"sha256": "7374180994e13cbca7b1dd4cf4da27c36926e462e844bbc0beb42e5ef6e562f5",
"status": "draft",
"maturity": "hypothesis",
"decision": "Make a navigable state reconstructible from its URL.",
@ -339,6 +339,12 @@
"family": "htmx in authored public-catalog fixture",
"ref": "research/reviews/NavigationHistoryExperiment.md",
"scope": "NH01–NH06: native/enhanced routes, history hit/miss, actual browser cache ordering and missing-Vary negative control; explicit Node validator checks separate."
},
{
"kind": "runtime",
"family": "htmx in authored public-catalog fixture",
"ref": "research/reviews/NavigationOwnershipExperiment.md",
"scope": "NO01–NO06: ordered history/success/error races, prior policy 0/12 full obligations versus guarded 12/12; separate focus ownership. Same fixture family; pinned history-XHR adapter."
}
],
"independent_full_contract_uses": null,
@ -350,8 +356,8 @@
"last_substantive_review_date": "2026-09-27"
},
"gaps": [
"Independent deployed full-contract recurrence remains unverified.",
"Keyboard/focus/announcements, late-script activation, interrupted transport, invalid/deleted routes, offline availability and real intermediary behavior remain open."
"Independent application recurrence and human/AT validation remain unverified.",
"Pending history disclosure, multiple history misses, redirects, nonzero swap delays, bfcache, lifecycle cleanup, interrupted/late enhancement and offline availability remain open."
],
"superseded_by": []
},
@ -359,7 +365,7 @@
"id": "CAND-0010",
"path": "research/candidates/ProgressiveEnhancement.md",
"title": "Progressive Enhancement",
"sha256": "ec3ab16d680308593af4d22fbbcbea737fe46096c5f3e65968d0db3a8befec21",
"sha256": "3eebc18862e1f5cd0221fa72f468a74719e7fbeab407cc81930d9e4ea397b3b5",
"status": "draft",
"maturity": "hypothesis",
"decision": "Keep essential actions usable when enhancement is unavailable.",
@ -387,6 +393,12 @@
"family": "htmx in authored public-catalog fixture",
"ref": "research/reviews/NavigationFocusExperiment.md",
"scope": "NF01–NF06: Tab/Enter navigation, raw versus authored focus/status policy, held-response ownership and background update; DOM evidence only, not AT speech."
},
{
"kind": "runtime",
"family": "htmx in authored public-catalog fixture",
"ref": "research/reviews/NavigationOwnershipExperiment.md",
"scope": "NO01–NO06: ordered history/success/error races, prior policy 0/12 full obligations versus guarded 12/12; separate focus ownership. Same fixture family; pinned history-XHR adapter."
}
],
"independent_full_contract_uses": null,
@ -398,8 +410,8 @@
"last_substantive_review_date": "2026-09-27"
},
"gaps": [
"Independent deployed full-contract recurrence and human/AT validation remain unverified.",
"Focus visibility, delayed history, overlapping requests, late-script activation, interrupted transport, invalid/deleted routes, offline availability and real intermediaries remain open."
"Independent application recurrence and human/AT validation remain unverified.",
"Pending history disclosure, multiple history misses, redirects, nonzero swap delays, bfcache, lifecycle cleanup, interrupted/late enhancement and offline availability remain open."
],
"superseded_by": []
},
@ -566,7 +578,7 @@
"id": "CAND-0016",
"path": "research/candidates/FocusReturn.md",
"title": "Focus Return",
"sha256": "129ef7623d11da91f3f5429686455be16180e5477d1706836f5c1c3617698eae",
"sha256": "a09b79e3b481caab81a7f9ee10d895ba77c6c7eedb71f4313733735eea58a477",
"status": "draft",
"maturity": "hypothesis",
"decision": "Restore meaningful focus after a temporary interaction ends.",
@ -594,6 +606,12 @@
"family": "htmx in authored public-catalog fixture",
"ref": "research/reviews/NavigationFocusExperiment.md",
"scope": "NF01–NF06: Tab/Enter navigation, raw versus authored focus/status policy, held-response ownership and background update; DOM evidence only, not AT speech."
},
{
"kind": "runtime",
"family": "htmx in authored public-catalog fixture",
"ref": "research/reviews/NavigationOwnershipExperiment.md",
"scope": "NO01–NO06: ordered history/success/error races, prior policy 0/12 full obligations versus guarded 12/12; separate focus ownership. Same fixture family; pinned history-XHR adapter."
}
],
"independent_full_contract_uses": null,
@ -607,7 +625,7 @@
"gaps": [
"One implementation family; repeated trials are not independent uses.",
"Animated exits and competing owners remain untested; no production repair claim.",
"Route continuation is a contrasting context, not independent modal-pattern recurrence; delayed history and AT speech unverified."
"Route ownership is a contrast, not independent modal recurrence; AT speech and broader history/lifecycle cases remain unverified."
],
"superseded_by": []
},

View file

@ -53,3 +53,24 @@ Raw rows observe behavior, so 22 passing rows do not mean raw and managed varian
meet identical focus/feedback obligations. See NF01–NF06. The policy is authored
experimental code; DOM focus and live-region text do not establish assistive-technology
announcements or conformance. History and request-race limits remain explicit.
## Competing completion follow-up and pause
[LEARN-0013 protocol](../../evolution/cycles/2026-09-27-navigation-ownership.md),
[review](../../reviews/NavigationOwnershipExperiment.md), [receipt](ownership-result.json).
Use the same browser environment with `node ownership.mjs`; choose a new
`FEP_RESULT_PATH` (default `/tmp/fep-ownership-result.json`). Six scenarios × two
policies × two browsers: 24 completed rows, all obligations met by guard 12/12 and
previous policy 0/12. These are deliberately adversarial cases, not general quality
scores. Server barriers impose schedules; client completions are observed without
aborting transport or claiming remote rollback.
The previous focus-policy.js remains byte-for-byte unchanged. New ownership-policy.js
uses a generation plus separate focus ownership; its history XHR handler wrapping is
explicitly tied to pinned internals. Initial runner/result retained under
ownership-initial.mjs / ownership-initial-result.json: four Firefox keyboard-helper
failures corrected by backward traversal, without changing policies or obligations.
Initial receipt's ownership.mjs hash corresponds to ownership-initial.mjs.
The [thread review](../../reviews/NavigationThreadReview.md) pauses fixture expansion
and hands the next evidence question to an independent pinned application source study.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,64 @@
import {chromium,firefox} 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 assert from 'node:assert/strict';
const dir=new URL('.',import.meta.url);
const assets=new Map(await Promise.all([['/htmx.js','node_modules/htmx.org/dist/htmx.js'],['/baseline.js','focus-policy.js'],['/guarded.js','ownership-policy.js']].map(async([url,file])=>[url,await readFile(new URL(file,dir))])));
const trace=`window.raceLog=[];for(const name of ['htmx:beforeRequest','htmx:historyCacheMiss'])document.addEventListener(name,e=>{const xhr=e.detail.xhr;xhr.addEventListener('load',()=>window.raceLog.push({event:'loaded',url:xhr.responseURL,status:xhr.status}));});`;
let requests=[],barriers=new Map();
function barrier(key){let resolve;const reached=new Promise(r=>resolve=r);const b={reached,resolve,release:null};barriers.set(key,b);return b;}
const server=createServer((req,res)=>{
const u=new URL(req.url,'http://local');res.setHeader('Cache-Control','no-store');
if(assets.has(u.pathname)){res.setHeader('Content-Type','application/javascript');res.end(assets.get(u.pathname));return;}
if(u.pathname==='/trace.js'){res.setHeader('Content-Type','application/javascript');res.end(trace);return;}
if(u.pathname!=='/articles'){res.writeHead(404);res.end('Not found');return;}
const topic=['alpha','beta','gamma'].includes(u.searchParams.get('topic'))?u.searchParams.get('topic'):'alpha';
const mode=u.searchParams.get('mode')==='guarded'?'guarded':'managed';
const hx=req.headers['hx-request']==='true',history=req.headers['hx-history-restore-request']==='true';
const status=hx&&u.searchParams.has('fail')?503:200;
const content=`<div id="payload"><h1 id="heading" tabindex="-1">${topic}</h1><p>Article ${topic}</p></div>`;
const boost='hx-boost="true" hx-target="#content" hx-select="#payload" hx-swap="innerHTML"';
const href=(t,extra='')=>`/articles?topic=${t}&mode=${mode}${extra}`;
const nav=`<nav><a id="beta" href="${href('beta')}" ${boost}>Visit beta</a><a id="old" href="${href('beta','&hold=old')}" ${boost}>Slow beta</a><a id="oldfail" href="${href('beta','&hold=old&fail=1')}" ${boost}>Slow failed beta</a><a id="new" href="${href('gamma')}" ${boost}>Visit gamma</a><a id="newfail" href="${href('gamma','&fail=1')}" ${boost}>Failed gamma</a><a id="recover" href="${href('gamma')}">Native recovery</a></nav>`;
const full=`<!doctype html><html lang="en"><head><meta charset="utf-8"><title>${topic} — Catalog</title><meta name="htmx-config" content='{"historyRestoreAsHxRequest":false}'><script src="/trace.js" defer></script><script src="/${mode==='guarded'?'guarded':'baseline'}.js" defer></script><script src="/htmx.js" defer></script></head><body>${nav}<main id="content" hx-history-elt>${content}</main><label>Persistent search <input id="search"></label><p id="feedback" role="status" aria-live="polite"></p></body></html>`;
const record={path:req.url,hx,history,status,held:false,released:false};requests.push(record);
const send=()=>{record.released=true;res.writeHead(status,{'Content-Type':'text/html'});res.end(status===503?'Injected failure':hx&&!history?content:full);};
const key=history?'history':u.searchParams.get('hold');const b=barriers.get(key);
if(b){record.held=true;b.release=send;b.resolve();}else send();
});
server.listen(0,'127.0.0.1');await once(server,'listening');const base=`http://127.0.0.1:${server.address().port}`;
const results=[],browsers={},output=process.env.FEP_RESULT_PATH||'/tmp/fep-ownership-result.json';
const inspect=p=>p.evaluate(()=>({topic:document.querySelector('#heading')?.textContent,url:location.pathname+location.search,title:document.title,focus:document.activeElement.id||document.activeElement.tagName,search:document.querySelector('#search').value,feedback:document.querySelector('#feedback').textContent}));
async function tabTo(p,id){for(let n=0;n<15;n++){if((await inspect(p)).focus===id)return;await p.keyboard.press('Tab');}throw Error(`Cannot Tab to ${id}`);}
async function activate(p,id){await tabTo(p,id);await p.keyboard.press('Enter');}
async function selected(p,t){await p.waitForFunction(t=>document.querySelector('#heading')?.textContent===t,t);await p.waitForFunction(t=>document.querySelector('#feedback').textContent===`Loaded ${t}`,t);}
async function released(p,b,history=false){b.release();b.release=null;await p.waitForFunction(history=>window.raceLog.some(e=>e.event==='loaded'&&(history?e.url.includes('topic=alpha'):e.url.includes('hold=old'))),history);}
async function held(b){await Promise.race([b.reached,new Promise((_,reject)=>setTimeout(()=>reject(Error('Server barrier not reached')),4000))]);}
function same(a,b,keys){return keys.every(k=>a[k]===b[k]);}
try{
for(const [engine,launcher]of [['chromium',chromium],['firefox',firefox]]){
const path=engine==='chromium'?process.env.FEP_CHROMIUM_PATH:undefined;const browser=await launcher.launch({headless:true,...(path?{executablePath:path}:{})});
browsers[engine]={version:browser.version(),executable:path||launcher.executablePath(),sha256:createHash('sha256').update(await readFile(path||launcher.executablePath())).digest('hex')};
try{for(const mode of ['managed','guarded']){
async function scenario(id,fn){
requests=[];barriers=new Map();const context=await browser.newContext();const p=await context.newPage();p.setDefaultTimeout(4500);
const r={id,engine,mode,status:'pass'};
try{await p.goto(base+`/articles?topic=alpha&mode=${mode}`);await p.waitForFunction(()=>window.htmx?.version==='2.0.11');Object.assign(r,await fn(p));r.obligationsMet=Object.values(r.obligations).every(Boolean);if(mode==='guarded')assert(r.obligationsMet,JSON.stringify(r.obligations));}
catch(e){r.status='fail';r.error=e.message;}
r.final=await inspect(p).catch(()=>null);r.events=await p.evaluate(()=>window.probe).catch(()=>[]);r.completions=await p.evaluate(()=>window.raceLog).catch(()=>[]);r.requests=[...requests];results.push(r);
for(const b of barriers.values())if(b.release){b.release();b.release=null;}await context.close();await writeFile(output,JSON.stringify({status:'incomplete',results},null,2)+'\n');
}
async function backMiss(p){await activate(p,'beta');await selected(p,'beta');await p.evaluate(()=>sessionStorage.removeItem('htmx-history-cache'));const b=barrier('history');await p.goBack();await held(b);return b;}
await scenario('O01-history-focus-moved',async p=>{const b=await backMiss(p);await tabTo(p,'search');await p.keyboard.type('new task');const before=await inspect(p);await released(p,b,true);const after=await inspect(p);return {before,after,obligations:{selection:after.topic==='alpha'&&new URL(base+after.url).searchParams.get('topic')==='alpha',title:after.title==='alpha — Catalog',focus:after.focus==='search',typedText:after.search==='new task'}};});
await scenario('O02-history-superseded',async p=>{const b=await backMiss(p);await activate(p,'new');await selected(p,'gamma');const before=await inspect(p);await released(p,b,true);const after=await inspect(p);return {before,after,obligations:{newerSelection:same(before,after,['topic','url','title']),noFeedbackOrFocusTheft:same(before,after,['feedback','focus'])}};});
await scenario('O03-old-success',async p=>{const b=barrier('old');await activate(p,'old');await held(b);await activate(p,'new');await selected(p,'gamma');const before=await inspect(p);await released(p,b);const after=await inspect(p);return {before,after,obligations:{newerSelection:same(before,after,['topic','url','title']),noFeedbackOrFocusTheft:same(before,after,['feedback','focus'])}};});
await scenario('O04-old-error',async p=>{const b=barrier('old');await activate(p,'oldfail');await held(b);await activate(p,'new');await selected(p,'gamma');const before=await inspect(p);await released(p,b);const after=await inspect(p);return {before,after,obligations:{newerSelection:same(before,after,['topic','url','title']),noObsoleteFeedback:same(before,after,['feedback','focus'])}};});
await scenario('O05-newer-fails',async p=>{const b=barrier('old');await activate(p,'old');await held(b);await activate(p,'newfail');await p.waitForFunction(()=>document.querySelector('#feedback').textContent.includes('Navigation failed'));const before=await inspect(p);assert.equal(before.topic,'alpha');await released(p,b);const after=await inspect(p);return {before,after,obligations:{committedView:same(before,after,['topic','url','title']),newestFailureRetained:same(before,after,['feedback','focus'])}};});
await scenario('O06-history-newer-than-request',async p=>{await activate(p,'beta');await selected(p,'beta');const b=barrier('old');await activate(p,'old');await held(b);await p.goBack();await selected(p,'alpha');const before=await inspect(p);await released(p,b);const after=await inspect(p);return {before,after,obligations:{historySelection:same(before,after,['topic','url','title']),historyContinuation:same(before,after,['feedback','focus'])}};});
}}finally{await browser.close();}
}
}finally{for(const b of barriers.values())if(b.release)b.release();await new Promise(resolve=>server.close(resolve));}
const files={};for(const f of ['ownership.mjs','ownership-policy.js','focus-policy.js','package.json','package-lock.json','node_modules/htmx.org/dist/htmx.js'])files[f]=createHash('sha256').update(await readFile(new URL(f,dir))).digest('hex');
const r={date:new Date().toISOString(),node:process.version,kind:'ordered-navigation-completion-races-in-authored-htmx-fixture',files,browsers,results,completed:results.filter(x=>x.status==='pass').length,total:results.length,obligationsMet:results.filter(x=>x.obligationsMet).length};await writeFile(output,JSON.stringify(r,null,2)+'\n');console.log(JSON.stringify({output,completed:r.completed,total:r.total,obligationsMet:r.obligationsMet,failures:results.filter(x=>x.status==='fail')},null,2));if(r.completed!==r.total)process.exitCode=1;

View file

@ -0,0 +1,31 @@
// Experimental adapter pinned to htmx 2.0.11; not a production router.
window.probe=[];
let generation=0,focusEpoch=0,historyMoveFocus=true;
const pending=new WeakMap();
document.addEventListener('focusin',()=>focusEpoch++);
window.addEventListener('popstate',()=>{generation++;},true);
const notice=text=>{document.querySelector('#feedback').textContent=text;};
function complete(move){const h=document.querySelector('#heading');document.title=`${h.textContent} — Catalog`;notice(`Loaded ${h.textContent}`);if(move)h.focus();}
document.addEventListener('htmx:beforeRequest',e=>{
if(e.detail.target.id==='content')pending.set(e.detail.xhr,{owner:++generation,source:e.detail.elt,move:false});
});
document.addEventListener('htmx:beforeOnLoad',e=>{
const state=pending.get(e.detail.xhr);
if(state&&state.owner!==generation){e.preventDefault();pending.delete(e.detail.xhr);window.raceLog.push({event:'discard-navigation',url:e.detail.xhr.responseURL});}
});
document.addEventListener('htmx:beforeSwap',e=>{const state=pending.get(e.detail.xhr);if(state)state.move=document.activeElement===state.source;});
document.addEventListener('htmx:afterSwap',e=>{const state=pending.get(e.detail.xhr);if(state){complete(state.move);pending.delete(e.detail.xhr);}});
document.addEventListener('htmx:responseError',e=>{if(pending.has(e.detail.xhr)){notice('Navigation failed. Current article retained. Use Native recovery to continue.');pending.delete(e.detail.xhr);}});
document.addEventListener('htmx:historyCacheHit',()=>{historyMoveFocus=true;});
document.addEventListener('htmx:historyCacheMiss',e=>{
const owner=generation,focusAtStart=focusEpoch,path=e.detail.path;
const xhr=e.detail.xhr,original=xhr.onload;
// The public miss event exposes XHR; wrapping its existing handler relies on the pinned implementation.
xhr.onload=function(event){
if(owner!==generation||location.pathname+location.search!==path){window.raceLog.push({event:'discard-history',url:xhr.responseURL});return;}
historyMoveFocus=focusEpoch===focusAtStart;
return original.call(this,event);
};
});
document.addEventListener('htmx:historyRestore',()=>complete(historyMoveFocus));
for(const name of ['htmx:afterSettle','htmx:historyRestore','htmx:responseError'])document.addEventListener(name,()=>window.probe.push({name,focus:document.activeElement.id||document.activeElement.tagName,title:document.title,url:location.href,feedback:document.querySelector('#feedback').textContent}));

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,64 @@
import {chromium,firefox} 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 assert from 'node:assert/strict';
const dir=new URL('.',import.meta.url);
const assets=new Map(await Promise.all([['/htmx.js','node_modules/htmx.org/dist/htmx.js'],['/baseline.js','focus-policy.js'],['/guarded.js','ownership-policy.js']].map(async([url,file])=>[url,await readFile(new URL(file,dir))])));
const trace=`window.raceLog=[];for(const name of ['htmx:beforeRequest','htmx:historyCacheMiss'])document.addEventListener(name,e=>{const xhr=e.detail.xhr;xhr.addEventListener('load',()=>window.raceLog.push({event:'loaded',url:xhr.responseURL,status:xhr.status}));});`;
let requests=[],barriers=new Map();
function barrier(key){let resolve;const reached=new Promise(r=>resolve=r);const b={reached,resolve,release:null};barriers.set(key,b);return b;}
const server=createServer((req,res)=>{
const u=new URL(req.url,'http://local');res.setHeader('Cache-Control','no-store');
if(assets.has(u.pathname)){res.setHeader('Content-Type','application/javascript');res.end(assets.get(u.pathname));return;}
if(u.pathname==='/trace.js'){res.setHeader('Content-Type','application/javascript');res.end(trace);return;}
if(u.pathname!=='/articles'){res.writeHead(404);res.end('Not found');return;}
const topic=['alpha','beta','gamma'].includes(u.searchParams.get('topic'))?u.searchParams.get('topic'):'alpha';
const mode=u.searchParams.get('mode')==='guarded'?'guarded':'managed';
const hx=req.headers['hx-request']==='true',history=req.headers['hx-history-restore-request']==='true';
const status=hx&&u.searchParams.has('fail')?503:200;
const content=`<div id="payload"><h1 id="heading" tabindex="-1">${topic}</h1><p>Article ${topic}</p></div>`;
const boost='hx-boost="true" hx-target="#content" hx-select="#payload" hx-swap="innerHTML"';
const href=(t,extra='')=>`/articles?topic=${t}&mode=${mode}${extra}`;
const nav=`<nav><a id="beta" href="${href('beta')}" ${boost}>Visit beta</a><a id="old" href="${href('beta','&hold=old')}" ${boost}>Slow beta</a><a id="oldfail" href="${href('beta','&hold=old&fail=1')}" ${boost}>Slow failed beta</a><a id="new" href="${href('gamma')}" ${boost}>Visit gamma</a><a id="newfail" href="${href('gamma','&fail=1')}" ${boost}>Failed gamma</a><a id="recover" href="${href('gamma')}">Native recovery</a></nav>`;
const full=`<!doctype html><html lang="en"><head><meta charset="utf-8"><title>${topic} — Catalog</title><meta name="htmx-config" content='{"historyRestoreAsHxRequest":false}'><script src="/trace.js" defer></script><script src="/${mode==='guarded'?'guarded':'baseline'}.js" defer></script><script src="/htmx.js" defer></script></head><body>${nav}<main id="content" hx-history-elt>${content}</main><label>Persistent search <input id="search"></label><p id="feedback" role="status" aria-live="polite"></p></body></html>`;
const record={path:req.url,hx,history,status,held:false,released:false};requests.push(record);
const send=()=>{record.released=true;res.writeHead(status,{'Content-Type':'text/html'});res.end(status===503?'Injected failure':hx&&!history?content:full);};
const key=history?'history':u.searchParams.get('hold');const b=barriers.get(key);
if(b){record.held=true;b.release=send;b.resolve();}else send();
});
server.listen(0,'127.0.0.1');await once(server,'listening');const base=`http://127.0.0.1:${server.address().port}`;
const results=[],browsers={},output=process.env.FEP_RESULT_PATH||'/tmp/fep-ownership-result.json';
const inspect=p=>p.evaluate(()=>({topic:document.querySelector('#heading')?.textContent,url:location.pathname+location.search,title:document.title,focus:document.activeElement.id||document.activeElement.tagName,search:document.querySelector('#search').value,feedback:document.querySelector('#feedback').textContent}));
async function tabTo(p,id){for(let n=0;n<15;n++){if((await inspect(p)).focus===id)return;const backwards=await p.evaluate(id=>{const a=document.activeElement,t=document.getElementById(id);return a!==document.body&&Boolean(a.compareDocumentPosition(t)&Node.DOCUMENT_POSITION_PRECEDING);},id);await p.keyboard.press(backwards?'Shift+Tab':'Tab');}throw Error(`Cannot Tab to ${id}`);}
async function activate(p,id){await tabTo(p,id);await p.keyboard.press('Enter');}
async function selected(p,t){await p.waitForFunction(t=>document.querySelector('#heading')?.textContent===t,t);await p.waitForFunction(t=>document.querySelector('#feedback').textContent===`Loaded ${t}`,t);}
async function released(p,b,history=false){b.release();b.release=null;await p.waitForFunction(history=>window.raceLog.some(e=>e.event==='loaded'&&(history?e.url.includes('topic=alpha'):e.url.includes('hold=old'))),history);}
async function held(b){await Promise.race([b.reached,new Promise((_,reject)=>setTimeout(()=>reject(Error('Server barrier not reached')),4000))]);}
function same(a,b,keys){return keys.every(k=>a[k]===b[k]);}
try{
for(const [engine,launcher]of [['chromium',chromium],['firefox',firefox]]){
const path=engine==='chromium'?process.env.FEP_CHROMIUM_PATH:undefined;const browser=await launcher.launch({headless:true,...(path?{executablePath:path}:{})});
browsers[engine]={version:browser.version(),executable:path||launcher.executablePath(),sha256:createHash('sha256').update(await readFile(path||launcher.executablePath())).digest('hex')};
try{for(const mode of ['managed','guarded']){
async function scenario(id,fn){
requests=[];barriers=new Map();const context=await browser.newContext();const p=await context.newPage();p.setDefaultTimeout(4500);
const r={id,engine,mode,status:'pass'};
try{await p.goto(base+`/articles?topic=alpha&mode=${mode}`);await p.waitForFunction(()=>window.htmx?.version==='2.0.11');Object.assign(r,await fn(p));r.obligationsMet=Object.values(r.obligations).every(Boolean);if(mode==='guarded')assert(r.obligationsMet,JSON.stringify(r.obligations));}
catch(e){r.status='fail';r.error=e.message;}
r.final=await inspect(p).catch(()=>null);r.events=await p.evaluate(()=>window.probe).catch(()=>[]);r.completions=await p.evaluate(()=>window.raceLog).catch(()=>[]);r.requests=[...requests];results.push(r);
for(const b of barriers.values())if(b.release){b.release();b.release=null;}await context.close();await writeFile(output,JSON.stringify({status:'incomplete',results},null,2)+'\n');
}
async function backMiss(p){await activate(p,'beta');await selected(p,'beta');await p.evaluate(()=>sessionStorage.removeItem('htmx-history-cache'));const b=barrier('history');await p.goBack();await held(b);return b;}
await scenario('O01-history-focus-moved',async p=>{const b=await backMiss(p);await tabTo(p,'search');await p.keyboard.type('new task');const before=await inspect(p);await released(p,b,true);const after=await inspect(p);return {before,after,obligations:{selection:after.topic==='alpha'&&new URL(base+after.url).searchParams.get('topic')==='alpha',title:after.title==='alpha — Catalog',focus:after.focus==='search',typedText:after.search==='new task'}};});
await scenario('O02-history-superseded',async p=>{const b=await backMiss(p);await activate(p,'new');await selected(p,'gamma');const before=await inspect(p);await released(p,b,true);const after=await inspect(p);return {before,after,obligations:{newerSelection:same(before,after,['topic','url','title']),noFeedbackOrFocusTheft:same(before,after,['feedback','focus'])}};});
await scenario('O03-old-success',async p=>{const b=barrier('old');await activate(p,'old');await held(b);await activate(p,'new');await selected(p,'gamma');const before=await inspect(p);await released(p,b);const after=await inspect(p);return {before,after,obligations:{newerSelection:same(before,after,['topic','url','title']),noFeedbackOrFocusTheft:same(before,after,['feedback','focus'])}};});
await scenario('O04-old-error',async p=>{const b=barrier('old');await activate(p,'oldfail');await held(b);await activate(p,'new');await selected(p,'gamma');const before=await inspect(p);await released(p,b);const after=await inspect(p);return {before,after,obligations:{newerSelection:same(before,after,['topic','url','title']),noObsoleteFeedback:same(before,after,['feedback','focus'])}};});
await scenario('O05-newer-fails',async p=>{const b=barrier('old');await activate(p,'old');await held(b);await activate(p,'newfail');await p.waitForFunction(()=>document.querySelector('#feedback').textContent.includes('Navigation failed'));const before=await inspect(p);assert.equal(before.topic,'alpha');await released(p,b);const after=await inspect(p);return {before,after,obligations:{committedView:same(before,after,['topic','url','title']),newestFailureRetained:same(before,after,['feedback','focus'])}};});
await scenario('O06-history-newer-than-request',async p=>{await activate(p,'beta');await selected(p,'beta');const b=barrier('old');await activate(p,'old');await held(b);await p.goBack();await selected(p,'alpha');const before=await inspect(p);await released(p,b);const after=await inspect(p);return {before,after,obligations:{historySelection:same(before,after,['topic','url','title']),historyContinuation:same(before,after,['feedback','focus'])}};});
}}finally{await browser.close();}
}
}finally{for(const b of barriers.values())if(b.release)b.release();await new Promise(resolve=>server.close(resolve));}
const files={};for(const f of ['ownership.mjs','ownership-policy.js','focus-policy.js','package.json','package-lock.json','node_modules/htmx.org/dist/htmx.js'])files[f]=createHash('sha256').update(await readFile(new URL(f,dir))).digest('hex');
const r={date:new Date().toISOString(),node:process.version,kind:'ordered-navigation-completion-races-in-authored-htmx-fixture',files,browsers,results,completed:results.filter(x=>x.status==='pass').length,total:results.length,obligationsMet:results.filter(x=>x.obligationsMet).length};await writeFile(output,JSON.stringify(r,null,2)+'\n');console.log(JSON.stringify({output,completed:r.completed,total:r.total,obligationsMet:r.obligationsMet,failures:results.filter(x=>x.status==='fail')},null,2));if(r.completed!==r.total)process.exitCode=1;

View file

@ -114,3 +114,12 @@ Q06 activation/reload follow-up: [conditional update and draft tests](../reviews
show why disjoint-intent retry differs from competing rollback, and why preflight
compatibility must be rechecked after reload. Same-tab draft preservation is narrower
than durability or remote-outcome recovery. No question or maturity gate is closed.
## Q05 navigation completion versus focus ownership — 2026-09-27
[NO01–NO06](../reviews/NavigationOwnershipExperiment.md) distinguish rights to apply
a navigation result from rights to move focus. Obsolete errors and history completions
can violate current intent even when the prior single-response policy seemed adequate.
Q05 remains open beyond one fixture family. [Thread review](../reviews/NavigationThreadReview.md)
pauses the fixture and proposes an independently developed application source contrast.
No new ownership pattern or canonical dimension is admitted.

View file

@ -40,3 +40,11 @@ accessibility work remains open. No maturity or M1/M2 promotion.
the provisional VC-03 subset. Explicit focus/title/status policy and delayed-response
ownership exercised; AT speech and full accessibility validation remain pending.
This is a new stream-F comparison group, not another comparable cache cycle.
## Ownership comparison and thread pause — 2026-09-27
[NO01–NO06](NavigationOwnershipExperiment.md) adds ordered competing-completion
evidence to provisional VC-03. Guarded policy meets 12/12 full obligations, prior
policy 0/12; one authored family, no AT/independent validation. The
[thread review](NavigationThreadReview.md) pauses local fixture expansion and names
a pinned Zulip navigation/search source contrast as the next evidence direction.

View file

@ -0,0 +1,95 @@
# Competing navigation completion ownership — 2026-09-27
LEARN-0013, Codex author-model review. [Preregistration](../evolution/cycles/2026-09-27-navigation-ownership.md)
committed as `e484cae` before implementation/execution. Stream F; new group
F-navigation-ownership-v1. [Runner](../experiments/navigation-history/ownership.mjs),
[guarded policy](../experiments/navigation-history/ownership-policy.js),
[final receipt](../experiments/navigation-history/ownership-result.json).
## Comparison and findings
Same locked htmx 2.0.11/Playwright 1.63.0 and Chromium 151.0.7922.34/Firefox 155.0.
The unchanged previous focus-policy.js is the baseline, on a new fixture with stable
content target and persistent navigation links. Different controls can have concurrent
requests; there is no hx-sync configuration. Authored ownership policy is a separate
variant, not a vendor patch. Exact input/browser hashes and completion traces retained.
All 24 rows completed in the corrected run. Baseline met all obligations in **0/12**
rows; guard met them in **12/12**. Baseline recording success is not obligation success.
These are six deliberately challenging scenarios in two browsers, not an application
quality score, independent trials or proof of all possible schedules.
| Claim | Case | Baseline in both browsers | Guarded result in both browsers |
| --- | --- | --- | --- |
| NO01 | O01 delayed history, newer search focus | Restores alpha and preserves typed text but moves focus from search to heading | Restores alpha while retaining search focus/text |
| NO02 | O02 delayed history, newer gamma navigation | Old alpha replaces content/title/status under gamma's URL | Gamma content/URL/title/status/focus unchanged |
| NO03 | O03 older success after newer success | Old beta replaces gamma and pushes beta's URL | Gamma result retained |
| NO04 | O04 older failure after newer success | Gamma remains but old failure replaces its success feedback | Gamma feedback retained |
| NO05 | O05 newer request fails, older success follows | Old beta resurfaces, replacing committed alpha and newest failure feedback | Alpha and newest failure retained |
| NO06 | O06 browser Back supersedes old request | Old beta replaces restored alpha and pushes beta's URL | Alpha history result retained |
O05 encodes an explicit latest-intent policy: a failed newer navigation does not revive
older intent without a new user decision. This is not a universal rule for every queue
or application. O01 distinguishes permission to restore content from permission to
move focus. During a pending Back miss, the browser URL already names alpha while beta
is still displayed; we measured completion obligations, not a complete pending-state
presentation/recovery design. No pending-URL consistency guarantee is established.
## Mechanism and implementation limits
Guarded navigation requests receive increasing generations; browser popstate advances
the same generation. Stale request completions are rejected at beforeOnLoad before
response processing. This covers success and error feedback, not just the content swap.
Focus checks remain separate from generation. History cache misses capture navigation
generation and a focus-change epoch. A still-current restoration can change the article
without moving focus after the user has started work in a surviving control.
The history adapter wraps the XHR onload handler exposed by historyCacheMiss and checks
the generation and target path before calling the original. The public
[htmx event reference](https://htmx.org/events/) documents request cancellation and
modifiable history-request details; **wrapping the preinstalled handler is a pinned
implementation-dependent experiment**, not a portable supported router API. Inspected
htmx 2.0.11 loadHistoryFromServer sets the handler before emitting historyCacheMiss;
its historyCacheMissLoad notification alone does not veto the subsequent swap. No
claim that this implementation will remain valid across versions or extensions.
No requests are aborted by this policy. Server traces show responses released and
client load events observed even when effects are discarded. Ignoring a completion is
not cancellation of server work, rollback, freshness verification or remote-write safety.
The fixture has no writes. A route generation is scoped to this one navigation surface,
not a global rule to suppress unrelated background work.
## Retained harness correction
[Initial receipt](../experiments/navigation-history/ownership-initial-result.json)
and [initial runner](../experiments/navigation-history/ownership-initial.mjs) preserve
20 completed rows and four Firefox harness failures. The forward-only Tab helper could
not return from the programmatically focused heading to earlier navigation controls in
O02/O06, in either policy. It did not execute those intended interleavings. The revised
helper chooses Shift+Tab when the target precedes the active element. No programmatic
focus, click, policy or assertion change was introduced. All 24 rows were rerun; initial
observations are not pooled as independent evidence. The initial runner file's hash
matches the initial receipt's ownership.mjs hash despite the archival filename.
## Learning disposition: revision
LEARN-0012 established only single-response continuation and explicitly left history/
request races unresolved. This follow-up does not retract those bounded results.
It does revise the language's operational guidance: checking focus at swap time is
insufficient as an overall completion policy. Distinguish navigation ownership from
focus ownership; apply navigation checks to history, titles, URL and feedback, including
late errors and the case where the newest request fails. Record one consequential
revision across affected candidates, not twelve discoveries or a new pattern.
URL-addressable State, Progressive Enhancement and the Focus Return scope boundary
remain hypotheses. Full-pattern recurrence, AT/human validation, repeated production
use, redirects, unload, nonzero swap/view-transition delays, multiple simultaneous
history misses, bfcache, pending-state disclosure and full lifecycle cleanup remain
unverified. The script has document-lifetime handlers; no mount/unmount reuse claim.
[Measured effort](../evolution/cycles/2026-09-27-navigation-ownership-effort.json)
includes the initial run and corrected run as execution. Active time includes protocol,
implementation, analysis and recording; final administration excluded. No token/cost
estimate or diminishing-returns claim. See the [navigation-thread review](NavigationThreadReview.md)
for the agreed pause and next evidence direction. Residuals remain WP-0003 T01/T02;
measurement work remains WP-0006 T03. No further fixture-hardening cycle is started.

View file

@ -0,0 +1,59 @@
# Navigation research thread review — 2026-09-27
Decision: pause expansion of the authored navigation fixture after LEARN-0013.
The user authorized a bounded ownership comparison followed by this review; completing
it does not close M1/M2, VC-03, the human-validation gate or the pattern language.
| Cycle | Contribution | Learning disposition |
| --- | --- | --- |
| [LEARN-0011](NavigationHistoryExperiment.md) | Reproduced direct/native/history behavior and fragment-as-document reuse without Vary; separated history snapshots from HTTP cache | Corroboration |
| [LEARN-0012](NavigationFocusExperiment.md) | Distinguished native operation, same-ID focus retention, route continuation and feedback; preserved newer search focus for a single response | Corroboration |
| [LEARN-0013](NavigationOwnershipExperiment.md) | Exposed old history/success/error effects after newer intent; separated completion ownership from focus ownership, including newer failure | Revision |
We now have executable support for a more precise public read-only navigation contract.
The third cycle changes operational guidance in existing candidates. It does not justify
another pattern solely because the fixture now has a generation counter. The modal
Focus Return candidate stays narrow; route continuation is a contrasting context.
## What these results do not establish
All three cycles share one authored htmx fixture family and one author-model reviewer.
Browser diversity strengthens the bounded mechanism observations; it does not supply
independent application recurrence. Screen-reader speech/usability, offline availability,
real intermediaries and deployed lifecycle behavior remain unverified. No fresh decision
usefulness evaluation was conducted; the frozen pilot's 18/24-versus-18/24 adjudication
and pending independent-human gate remain unchanged.
Effort is recorded prospectively in each cycle's receipt. The protocols address different
question scopes: B-navigation-mechanism-v1, F-navigation-focus-v1 and
F-navigation-ownership-v1. They are **not three comparable observations of a learning
curve**. Do not infer acceleration, diminishing returns or a plateau from their rates.
Two corroborations followed by a revision are an account of this investigation, not a
productivity ranking. Harness correction effort remains visible rather than erased.
## Why pause, and what follows
Further fixture work could improve this adapter while leaving the main evidence gap
unchanged: whether the candidate contracts recur in independently developed applications.
Our next bounded direction is a **pinned, read-only Zulip navigation/search source
walkthrough** under WP-0003 T01. Ask where that application accepts, supersedes or
reports asynchronous navigation/search results, and how selection and focus ownership
are separated—or whether the proposed model does not fit. Reuse the existing provenance
workflow, pin the chosen revision, and cite exact mechanisms and counterexamples.
Do not count Zulip's previously inspected draft/send paths as new navigation evidence.
Static inspection would remain static evidence, not proof of deployed runtime behavior.
Register the question before execution; no additional model calls or study starts here.
Keep these residuals live, without automatically extending the fixture:
- WP-0003 T01: pending Back-state disclosure/recovery, multiple history misses,
redirects, nonzero swap delays, bfcache and lifecycle teardown before any production
recommendation; interrupted and late-script enhancement remains untested.
- WP-0003 T02: independent human/assistive-technology review and the offline-reading
branch of provisional VC-03. DOM live-region checks do not close these obligations.
- WP-0006 T03: reconcile remaining substantive-review dates and assess returns only
when useful comparable cycles exist. Do not manufacture cycles to satisfy a count.
Reopen the local fixture when independent evidence exposes a specific contradiction
or a concrete consumer needs one of these limits resolved. The next source contrast
can narrow, merge or reject our claims; promotion is not its default outcome.

View file

@ -117,3 +117,14 @@ to persistent search. The managed policy passes its DOM obligations; raw same-ID
focus retention does not itself update title or report errors in this fragment host.
VC-03 stays provisional. Spoken feedback, focus visibility, delayed history/request
races and offline availability remain open; no complete accessibility claim.
## Competing navigation and bounded pause — 2026-09-27
[NO01–NO06](../reviews/NavigationOwnershipExperiment.md) show why result ownership
applies to content, URL, title and feedback separately from focus ownership. Old
history may overwrite newer content under a different URL; an old error may overwrite
newer success feedback. The guarded latest-intent policy handles the tested schedules,
including preserving committed content when the newest request fails. It is not a
general router or AT validation. Pending Back-state disclosure and broader history
lifecycle remain open. [Thread review](../reviews/NavigationThreadReview.md) pauses
fixture expansion; VC-03 stays provisional with independent/AT and offline gaps.

View file

@ -215,3 +215,21 @@ WP-0003 T01 next: delayed history restoration and competing-navigation ownership
interrupted/late enhancement remains open. T02 retains AT/independent review and
offline branch. WP-0006 T03 remains progress for comparable windows and remaining
review-date reconciliation. No M1/M2 completion.
## Completion ownership compared; navigation fixture paused — 2026-09-27
LEARN-0013 preregistered at e484cae; 24 completed rows after a retained keyboard-helper
correction. Prior policy meets full obligations in 0/12, authored guard 12/12. Record
one revision: check navigation completion ownership separately from focus permission,
including history, errors and newer-request failure. Same authored family; hypotheses
and provisional VC-03 retained. See research/reviews/NavigationOwnershipExperiment.md
and NavigationThreadReview.md. Three distinct cycle groups do not establish a trend.
Agreed pause: do not automatically extend the navigation fixture. Next bounded
WP-0003 T01 question is a preregistered, pinned read-only Zulip navigation/search
source contrast: locate selection/result/focus ownership or counterexamples outside
the authored fixture; prior draft/send evidence does not count as navigation support.
T01 retains pending history disclosure, multiple misses, redirects, delayed swaps,
bfcache/lifecycle and interrupted/late enhancement before any production claim.
T02 retains independent human/AT review and offline VC-03 branch. WP-0006 T03 retains
review-date reconciliation and useful comparable-cycle windows; no M1/M2 closure.

View file

@ -94,3 +94,21 @@ WP-0003 T01 next: delayed history restoration and competing-navigation ownership
interrupted/late enhancement remains open. T02 retains AT/independent review and
offline branch. WP-0006 T03 remains progress for comparable windows and remaining
review-date reconciliation. No M1/M2 completion.
## Completion ownership compared; navigation fixture paused — 2026-09-27
LEARN-0013 preregistered at e484cae; 24 completed rows after a retained keyboard-helper
correction. Prior policy meets full obligations in 0/12, authored guard 12/12. Record
one revision: check navigation completion ownership separately from focus permission,
including history, errors and newer-request failure. Same authored family; hypotheses
and provisional VC-03 retained. See research/reviews/NavigationOwnershipExperiment.md
and NavigationThreadReview.md. Three distinct cycle groups do not establish a trend.
Agreed pause: do not automatically extend the navigation fixture. Next bounded
WP-0003 T01 question is a preregistered, pinned read-only Zulip navigation/search
source contrast: locate selection/result/focus ownership or counterexamples outside
the authored fixture; prior draft/send evidence does not count as navigation support.
T01 retains pending history disclosure, multiple misses, redirects, delayed swaps,
bfcache/lifecycle and interrupted/late enhancement before any production claim.
T02 retains independent human/AT review and offline VC-03 branch. WP-0006 T03 retains
review-date reconciliation and useful comparable-cycle windows; no M1/M2 closure.