From e8eb76ae1fe651bef9ecccd66f6e28376457b8f2 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 9 Oct 2026 18:41:57 +0000 Subject: [PATCH 1/3] Museum: render SIDE24 packet cards in manifest order; name a refused packet What: museum.mjs now appends one placeholder host per packet in manifest order before any fetch (the claim-card pattern) and each packet job fills its own host with replaceChildren. A refused packet keeps the title 'Packet source' and its error paragraph, now carries the packet id as its element id and adds a plain 'Packet ID: ' line. One job per packet is kept, so the unavailable-view count, the empty-manifest message, progressive display and the startup-failure path are unchanged. Why (SIDE24-ORDER-1): packet cards were appended after their RESULT.md fetch resolved, so their order followed network completion. With live fetches, 4 of 12 museum.html loads rendered chart-claude before identity-replay, while the status line still read verified. A held-route repro (identity-replay RESULT.md held 1,500 ms) swapped them every time. SIDE24-ORDER-2: a refused packet card had no id and nothing naming the packet. Tests: tests/test_museum_frontend.mjs adds two node tests (first packet resolves last; a refusal keeps its slot) that fail on the old museum.mjs and pass now, and the refusal cases assert the refused card's id and its 'Packet ID:' line. tools/public_shop_browser_check.py adds a held-route guard to the museum-reader-intent case: it holds the first packet's RESULT.md, waits for the second packet's article element, releases, and requires the #packet-cards article ids to equal the manifest packet ids; the observed order is recorded as packet_order. Not touched: payloads (museum.json, status.json, config.json, observations.json, dependency-source), page text, docs/site/README.md, geometry.mjs and its test. The other 21 docs/site files change only their ?site-release= key, regenerated by tools/site_asset_release.py. Release key: f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01S16677s1SGeAJmN3QMz4ip --- docs/site/app.js | 6 ++--- docs/site/brand.css | 2 +- docs/site/cite.html | 4 +-- docs/site/cite.mjs | 2 +- docs/site/conditionals.mjs | 2 +- docs/site/curvature-export.mjs | 2 +- docs/site/dependencies.html | 10 ++++---- docs/site/dependencies.mjs | 2 +- docs/site/explore-models.mjs | 2 +- docs/site/explore.html | 10 ++++---- docs/site/explore.mjs | 12 ++++----- docs/site/formal.html | 6 ++--- docs/site/index.html | 4 +-- docs/site/measure.html | 10 ++++---- docs/site/measure.mjs | 2 +- docs/site/museum.html | 6 ++--- docs/site/museum.mjs | 10 +++++--- docs/site/palette-export.mjs | 4 +-- docs/site/pin-export.mjs | 4 +-- docs/site/reproduce.html | 4 +-- docs/site/research.html | 2 +- docs/site/workspace.html | 6 ++--- tests/test_museum_frontend.mjs | 40 ++++++++++++++++++++++++++++++ tools/public_shop_browser_check.py | 20 +++++++++++++++ 24 files changed, 117 insertions(+), 55 deletions(-) diff --git a/docs/site/app.js b/docs/site/app.js index 5e6cea4e8..83e7618f0 100644 --- a/docs/site/app.js +++ b/docs/site/app.js @@ -1,6 +1,6 @@ -import {verifiedJSON, verifiedBytes, validateStatus, validateCoefficient, safeSourceURL, hex40, hex64} from './core.mjs?site-release=05e781a3ffc6330db3d2460bb7eb11e90e9fa8f9ee06a1364997e6b209299238'; -import {connectCatalogQuery} from './catalog-query.mjs?site-release=05e781a3ffc6330db3d2460bb7eb11e90e9fa8f9ee06a1364997e6b209299238'; -import {prepareWorkspaceFragment} from './workspace-fragment.mjs?site-release=05e781a3ffc6330db3d2460bb7eb11e90e9fa8f9ee06a1364997e6b209299238'; +import {verifiedJSON, verifiedBytes, validateStatus, validateCoefficient, safeSourceURL, hex40, hex64} from './core.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; +import {connectCatalogQuery} from './catalog-query.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; +import {prepareWorkspaceFragment} from './workspace-fragment.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; const finishFragment=prepareWorkspaceFragment(); const el=id=>document.getElementById(id); const node=(tag,text,cls)=>{const n=document.createElement(tag);if(text!==undefined)n.textContent=text;if(cls)n.className=cls;return n;}; diff --git a/docs/site/brand.css b/docs/site/brand.css index 39ebe3c41..e3285642e 100644 --- a/docs/site/brand.css +++ b/docs/site/brand.css @@ -60,7 +60,7 @@ body > header { color: var(--ul-text); font-weight: 750; letter-spacing: .13em; position: relative; padding-left: 57px; min-height: 44px; line-height: 1.65; } -.brand::before { content: ""; position: absolute; left: 0; top: 1px; width: 42px; height: 42px; background: url("brand/mark.svg?site-release=05e781a3ffc6330db3d2460bb7eb11e90e9fa8f9ee06a1364997e6b209299238") center/contain no-repeat; } +.brand::before { content: ""; position: absolute; left: 0; top: 1px; width: 42px; height: 42px; background: url("brand/mark.svg?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b") center/contain no-repeat; } .brand span { color: var(--ul-muted); font-size: 12px; letter-spacing: .16em; text-transform: uppercase; } nav { gap: 12px 24px; flex-wrap: wrap; } nav a { color: var(--ul-text); min-height: 44px; display: inline-flex; align-items: center; font-size: 13px; padding: 0 6px; margin: 0 -6px; } diff --git a/docs/site/cite.html b/docs/site/cite.html index a4b5b7c20..fbd5e330f 100644 --- a/docs/site/cite.html +++ b/docs/site/cite.html @@ -4,8 +4,8 @@ Cite · Universal Law - - + +
UNIVERSAL LAW MATHEMATICS IN THE OPEN
diff --git a/docs/site/cite.mjs b/docs/site/cite.mjs index 5bfd97ef0..92b35664c 100644 --- a/docs/site/cite.mjs +++ b/docs/site/cite.mjs @@ -1,4 +1,4 @@ -import {wireReferenceForm} from './source-reference.mjs?site-release=05e781a3ffc6330db3d2460bb7eb11e90e9fa8f9ee06a1364997e6b209299238'; +import {wireReferenceForm} from './source-reference.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; const byId = id => document.getElementById(id); wireReferenceForm({form:byId('reference-form'), repository:byId('reference-repository'), commit:byId('reference-commit'), path:byId('reference-path'), sha256:byId('reference-sha256'), output:byId('reference-output'), json:byId('reference-json'), bibtex:byId('reference-bibtex'), link:byId('reference-link'), status:byId('reference-status'), actions:byId('reference-actions'), copyText:byId('reference-copy'), copyJSON:byId('reference-copy-json'), copyBibTeX:byId('reference-copy-bibtex'), share:byId('reference-share'), clipboard:globalThis.navigator?.clipboard, location:globalThis.location, history:globalThis.history, events:globalThis.window}); byId('reference-builder').hidden = false; diff --git a/docs/site/conditionals.mjs b/docs/site/conditionals.mjs index 47744d1f5..656248a62 100644 --- a/docs/site/conditionals.mjs +++ b/docs/site/conditionals.mjs @@ -93,7 +93,7 @@ export function renderProjection(document,host,data,source){ el(document,'p','Generated from verified source bytes. This graph is not formal verification, a proof review, or a scientific-status register.')); host.replaceChildren(article); } -const services=()=>import('./museum.mjs?site-release=05e781a3ffc6330db3d2460bb7eb11e90e9fa8f9ee06a1364997e6b209299238'); +const services=()=>import('./museum.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'); // The museum module already verifies config → manifest → index/status once per // page fetch and caches every pinned byte request. This route consumes that same // verified startup, so it adds only the audited-descriptor check and projection. diff --git a/docs/site/curvature-export.mjs b/docs/site/curvature-export.mjs index c12ee9f21..a21f4b9d4 100644 --- a/docs/site/curvature-export.mjs +++ b/docs/site/curvature-export.mjs @@ -1,6 +1,6 @@ // Bounded, local export of the Peaks and saddles teaching diagram. No source fetch // or proof verification occurs here; the identity below is recorded provenance. -import { coneModel } from './explore-models.mjs?site-release=05e781a3ffc6330db3d2460bb7eb11e90e9fa8f9ee06a1364997e6b209299238'; +import { coneModel } from './explore-models.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; const SVG_NS = 'http://www.w3.org/2000/svg'; const PUBLIC_EXPLORE = 'https://d6g8k5htny-coder.github.io/main/site/explore.html'; diff --git a/docs/site/dependencies.html b/docs/site/dependencies.html index bfdfb4a77..469cafe8f 100644 --- a/docs/site/dependencies.html +++ b/docs/site/dependencies.html @@ -7,11 +7,11 @@ Claim dependencies · Universal Law - - - - - + + + + + diff --git a/docs/site/dependencies.mjs b/docs/site/dependencies.mjs index 1a5201d93..d2fc74197 100644 --- a/docs/site/dependencies.mjs +++ b/docs/site/dependencies.mjs @@ -12,7 +12,7 @@ import { recordedContextRows, recordedReviewText, unresolvedTargets, -} from './dependency-model.mjs?site-release=05e781a3ffc6330db3d2460bb7eb11e90e9fa8f9ee06a1364997e6b209299238'; +} from './dependency-model.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; const byId = id => document.getElementById(id); diff --git a/docs/site/explore-models.mjs b/docs/site/explore-models.mjs index edb0709b8..156a74c7c 100644 --- a/docs/site/explore-models.mjs +++ b/docs/site/explore-models.mjs @@ -1,6 +1,6 @@ // NON-CERTIFYING educational arithmetic. These functions illustrate source // definitions; they do not simulate a field or estimate a theorem's constants. -import { annulusModel, p15Model, remoteModel } from './geometry.mjs?site-release=05e781a3ffc6330db3d2460bb7eb11e90e9fa8f9ee06a1364997e6b209299238'; +import { annulusModel, p15Model, remoteModel } from './geometry.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; function finite(value, name) { if (!Number.isFinite(value)) throw new RangeError(`${name} must be finite`); diff --git a/docs/site/explore.html b/docs/site/explore.html index 221562cd8..78391e792 100644 --- a/docs/site/explore.html +++ b/docs/site/explore.html @@ -7,11 +7,11 @@ Explore the mathematics · Universal Law - - - - - + + + + + diff --git a/docs/site/explore.mjs b/docs/site/explore.mjs index 31c4d7e52..007939169 100644 --- a/docs/site/explore.mjs +++ b/docs/site/explore.mjs @@ -1,10 +1,10 @@ -import { coneModel, pinModel, paletteModel, applyCurvaturePreset } from './explore-models.mjs?site-release=05e781a3ffc6330db3d2460bb7eb11e90e9fa8f9ee06a1364997e6b209299238'; -import { readExploreState, exploreStateURL } from './explore-state.mjs?site-release=05e781a3ffc6330db3d2460bb7eb11e90e9fa8f9ee06a1364997e6b209299238'; +import { coneModel, pinModel, paletteModel, applyCurvaturePreset } from './explore-models.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; +import { readExploreState, exploreStateURL } from './explore-state.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; -import { captureCurvatureDiagram, serializeCurvatureSVG, createCitationController } from './curvature-export.mjs?site-release=05e781a3ffc6330db3d2460bb7eb11e90e9fa8f9ee06a1364997e6b209299238'; -import { captureTeachingDiagram, createFigureCitationController } from './teaching-export.mjs?site-release=05e781a3ffc6330db3d2460bb7eb11e90e9fa8f9ee06a1364997e6b209299238'; -import { serializePinSVG, pinFigureCitation } from './pin-export.mjs?site-release=05e781a3ffc6330db3d2460bb7eb11e90e9fa8f9ee06a1364997e6b209299238'; -import { serializePaletteSVG, paletteFigureCitation } from './palette-export.mjs?site-release=05e781a3ffc6330db3d2460bb7eb11e90e9fa8f9ee06a1364997e6b209299238'; +import { captureCurvatureDiagram, serializeCurvatureSVG, createCitationController } from './curvature-export.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; +import { captureTeachingDiagram, createFigureCitationController } from './teaching-export.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; +import { serializePinSVG, pinFigureCitation } from './pin-export.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; +import { serializePaletteSVG, paletteFigureCitation } from './palette-export.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; const ns = 'http://www.w3.org/2000/svg'; const byId = id => document.getElementById(id); diff --git a/docs/site/formal.html b/docs/site/formal.html index 894738524..e76487430 100644 --- a/docs/site/formal.html +++ b/docs/site/formal.html @@ -5,9 +5,9 @@ Formal verification · Universal Law - - - + + + diff --git a/docs/site/index.html b/docs/site/index.html index 08f0174b9..f3c6b13bb 100644 --- a/docs/site/index.html +++ b/docs/site/index.html @@ -4,8 +4,8 @@ The shape of randomness · Universal Law - - + +
UNIVERSAL LAW MATHEMATICS IN THE OPEN
diff --git a/docs/site/measure.html b/docs/site/measure.html index fb547aa7a..d6cc7594a 100644 --- a/docs/site/measure.html +++ b/docs/site/measure.html @@ -7,11 +7,11 @@ From counts to density · Universal Law - - - - - + + + + + diff --git a/docs/site/measure.mjs b/docs/site/measure.mjs index 149f46a23..df32b66b4 100644 --- a/docs/site/measure.mjs +++ b/docs/site/measure.mjs @@ -1,4 +1,4 @@ -import { measurementModel, parseMeasurementInputs } from './measurement-model.mjs?site-release=05e781a3ffc6330db3d2460bb7eb11e90e9fa8f9ee06a1364997e6b209299238'; +import { measurementModel, parseMeasurementInputs } from './measurement-model.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; const byId = id => document.getElementById(id); const fields = Object.fromEntries(['count', 'realizations', 'lower', 'upper'].map(name => [name, byId(`measure-${name}`)])); diff --git a/docs/site/museum.html b/docs/site/museum.html index b0c2495af..7676cdbc0 100644 --- a/docs/site/museum.html +++ b/docs/site/museum.html @@ -5,11 +5,11 @@ Source records & exhibits · Universal Law - + - - + + diff --git a/docs/site/museum.mjs b/docs/site/museum.mjs index 14a58ec2a..46b2a562f 100644 --- a/docs/site/museum.mjs +++ b/docs/site/museum.mjs @@ -1,5 +1,5 @@ -import {renderSourceQuote} from './source-quote.mjs?site-release=05e781a3ffc6330db3d2460bb7eb11e90e9fa8f9ee06a1364997e6b209299238'; -import {verifiedBytes, hex40, hex64} from './core.mjs?site-release=05e781a3ffc6330db3d2460bb7eb11e90e9fa8f9ee06a1364997e6b209299238'; +import {renderSourceQuote} from './source-quote.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; +import {verifiedBytes, hex40, hex64} from './core.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; export const DISCLAIMER='This canvas explains the pinned source. It is not a proof and does not change status.'; const REVIEWED_IDS=['d2-lifetime-remainder','d3-side24-coefficient','d4-fixed-remote-rn','d5-all-height-annulus','d5-height-window-annulus','d5-two-scale','d5-inner-belt-density','d5-fixed-transverse','cumulative-transfer-correction','p15-demand-one-counterexample','d6-p15-full-price']; @@ -264,7 +264,7 @@ function prepareClaimFragment(document,window,currentHash) { return true; }; } -export async function startMuseum({document=globalThis.document,window=globalThis.window,fetcher=globalThis.fetch,search=globalThis.location?.search||'',geometryLoader=()=>import('./geometry.mjs?site-release=05e781a3ffc6330db3d2460bb7eb11e90e9fa8f9ee06a1364997e6b209299238'),currentHash=()=>globalThis.location?.hash||''}={}){ +export async function startMuseum({document=globalThis.document,window=globalThis.window,fetcher=globalThis.fetch,search=globalThis.location?.search||'',geometryLoader=()=>import('./geometry.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'),currentHash=()=>globalThis.location?.hash||''}={}){ const ids=['museum-state','claim-cards','lifetime-fixture','packet-cards','active-exhibit']; const containers=Object.fromEntries(ids.map(id=>{const node=document.getElementById(id);if(!node)throw Error(`Missing museum container: ${id}`);return [id,node];})); const finishFragment=prepareClaimFragment(document,window,currentHash); @@ -276,7 +276,9 @@ export async function startMuseum({document=globalThis.document,window=globalThi const jobs=manifest.claims.map(async(claim,i)=>{try{await verifyClaim(claim,cachedFetch);placeholders[i].replaceChildren(renderClaim(document,claim,REVIEWED_IDS.includes(claim.id)?manifest.index_source:manifest.status_source));}catch(error){const unavailable=refusal(document,claim.title,error);unavailable.id=claim.id;placeholders[i].replaceChildren(unavailable);return false;}return true;}); jobs.push((async()=>{try{containers['lifetime-fixture'].replaceChildren(await renderLifetime(document,manifest.exhibits.lifetime,cachedFetch));return true;}catch(error){containers['lifetime-fixture'].replaceChildren(refusal(document,'D2 lifetime fixture',error));return false;}})()); if(!manifest.packets.length)containers['packet-cards'].append(element(document,'p','No packet appears in this pinned projection.')); - for(const packet of manifest.packets)jobs.push((async()=>{try{containers['packet-cards'].append(await renderPacket(document,packet,cachedFetch));return true;}catch(error){containers['packet-cards'].append(refusal(document,'Packet source',error));return false;}})()); + // One host per packet, appended in manifest order before any fetch, as for the claim cards: a card fills its own slot whenever its bytes arrive, and a refusal names its packet. + const packetHosts=manifest.packets.map(packet=>{const host=element(document,'div');host.append(element(document,'p',`Verifying ${packet.id}…`));containers['packet-cards'].append(host);return host;}); + manifest.packets.forEach((packet,i)=>jobs.push((async()=>{try{packetHosts[i].replaceChildren(await renderPacket(document,packet,cachedFetch));return true;}catch(error){const unavailable=refusal(document,'Packet source',error);unavailable.id=packet.id;unavailable.append(element(document,'p',`Packet ID: ${packet.id}`));packetHosts[i].replaceChildren(unavailable);return false;}})())); const kind=new URLSearchParams(search).get('view'); if(kind){ if(!Object.hasOwn(EXHIBITS,kind)){containers['active-exhibit'].append(refusal(document,'Unknown exhibit',Error('Select one of the four declared source illustrations')));jobs.push(false);} diff --git a/docs/site/palette-export.mjs b/docs/site/palette-export.mjs index 30e002757..8bbc58e52 100644 --- a/docs/site/palette-export.mjs +++ b/docs/site/palette-export.mjs @@ -1,7 +1,7 @@ // Bounded teaching export of Explore's current finite P15 diagram. Source // custody is provenance; this module performs no mathematical verification. -import { paletteModel } from './explore-models.mjs?site-release=05e781a3ffc6330db3d2460bb7eb11e90e9fa8f9ee06a1364997e6b209299238'; -import { serializeTeachingSVG } from './teaching-export.mjs?site-release=05e781a3ffc6330db3d2460bb7eb11e90e9fa8f9ee06a1364997e6b209299238'; +import { paletteModel } from './explore-models.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; +import { serializeTeachingSVG } from './teaching-export.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; const PUBLIC_EXPLORE = 'https://d6g8k5htny-coder.github.io/main/site/explore.html'; const SOURCE = Object.freeze({ diff --git a/docs/site/pin-export.mjs b/docs/site/pin-export.mjs index 63387c29e..edcfed45c 100644 --- a/docs/site/pin-export.mjs +++ b/docs/site/pin-export.mjs @@ -1,7 +1,7 @@ // Bounded export of the CURRENT displayed Approach-points teaching diagram. // Pinned source identities are provenance, not proof acceptance or verification. -import { pinModel } from './explore-models.mjs?site-release=05e781a3ffc6330db3d2460bb7eb11e90e9fa8f9ee06a1364997e6b209299238'; -import { serializeTeachingSVG } from './teaching-export.mjs?site-release=05e781a3ffc6330db3d2460bb7eb11e90e9fa8f9ee06a1364997e6b209299238'; +import { pinModel } from './explore-models.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; +import { serializeTeachingSVG } from './teaching-export.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; const PUBLIC_EXPLORE='https://d6g8k5htny-coder.github.io/main/site/explore.html'; const COMMIT='d6628da09384728992dcbe6e921cc28ba85aebb0'; diff --git a/docs/site/reproduce.html b/docs/site/reproduce.html index 8c7ac2b5b..3c2a49a79 100644 --- a/docs/site/reproduce.html +++ b/docs/site/reproduce.html @@ -4,9 +4,9 @@ Reproduce · Universal Law - + - +
UNIVERSAL LAW MATHEMATICS IN THE OPEN
diff --git a/docs/site/research.html b/docs/site/research.html index 632908da0..616f54dec 100644 --- a/docs/site/research.html +++ b/docs/site/research.html @@ -4,7 +4,7 @@ Research · Universal Law - +
UNIVERSAL LAW MATHEMATICS IN THE OPEN
diff --git a/docs/site/workspace.html b/docs/site/workspace.html index 7c6788e5a..f60c08487 100644 --- a/docs/site/workspace.html +++ b/docs/site/workspace.html @@ -5,9 +5,9 @@ Library and research workspace · Universal Law - - - + + + diff --git a/tests/test_museum_frontend.mjs b/tests/test_museum_frontend.mjs index 3a841161c..920ff36f5 100644 --- a/tests/test_museum_frontend.mjs +++ b/tests/test_museum_frontend.mjs @@ -233,6 +233,9 @@ test('refused cards carry no role and are not live; the museum status line count const page=await renderRefusals('',{proofText:'Changed proof.',packet:true}); assert.ok(page.refused.length>2,'claim cards, the D2 fixture and the packet are refused'); assert.equal(page.refused.filter(n=>n.children[0].textContent==='Packet source').length,1,'the packet whose source fails is refused'); + const packetCard=page.refused.find(n=>n.children[0].textContent==='Packet source'); + assert.equal(packetCard.id,'side24-identity-replay-20260926','the refused packet card carries its packet id'); + assert.ok(packetCard.children.some(n=>n.tagName==='P'&&n.textContent==='Packet ID: side24-identity-replay-20260926'),'the refused packet card names its packet'); assert.deepEqual(page.roled.map(n=>n.textContent),[],'refused cards and their notes are not live regions'); for(const card of page.refused)assert.ok(card.children.some(n=>n.tagName==='P'&&n.className==='error'&&/^Unavailable: .+\. No result inferred\.$/.test(n.textContent))); assert.equal(page.state,countedViews(page.refused.length)); @@ -376,3 +379,40 @@ test('readable presentation retains the exact original source quote in a disclos // The museum CI integration suite includes source-quote projection controls. import "./test_source_quote.mjs"; + +// Packet cards keep manifest order whatever order their RESULT.md bytes arrive in: the first +// declared packet is held until the second has rendered, and a refusal keeps its own slot. +async function packetOrderPage({failSecond=false}={}){ + const f=fixture(),ids=['side24-identity-replay-20260926','side24-chart-claude-20260926'],commits=['71400b94f6cb354a8cf7aba73ffede2138a64efa','a12c178c0f857a130cf434e9efd44233a038195b']; + const text='Scientific effect: **NONE**\nReview status: **REVIEW_REQUIRED**\n'; + const source=(i,name)=>{const path=`incoming/${ids[i]}/${name}`;return {...pin(path,text,commits[i]),repository:'d6g8k5htny-coder/main',url:`https://raw.githubusercontent.com/d6g8k5htny-coder/main/${commits[i]}/${path}`,html_url:`https://github.com/d6g8k5htny-coder/main/blob/${commits[i]}/${path}`};}; + f.manifest.packets=ids.map((id,i)=>({id,issue:i?141:null,result:source(i,'RESULT.md'),identity:source(i,'IDENTITY.json'),output:source(i,'output.json'),scientific_effect:'NONE',review_status:'REVIEW_REQUIRED'})); + const raw=JSON.stringify(f.manifest),document=documentFromHTML(); + const mapping=new Map([['config.json',JSON.stringify({museum_json:{url:'museum.json',bytes:Buffer.byteLength(raw),sha256:digest(raw)}})],['museum.json',raw],[f.manifest.index_source.url,f.index],[f.manifest.status_source.url,f.status],[f.manifest.claims[0].proof.url,'Pinned proof.'],[f.manifest.claims[11].review.url,'Pinned reconciliation.'],[f.manifest.packets[0].result.url,text],[f.manifest.packets[1].result.url,failSecond?text+'changed':text]]); + let release,secondServed;const gate=new Promise(resolve=>release=resolve),served=new Promise(resolve=>secondServed=resolve); + const loading=museum.startMuseum({document,search:'',fetcher:async url=>{ + url=String(url); + if(url===f.manifest.packets[0].result.url){await served;await new Promise(resolve=>setTimeout(resolve,0));await gate;} + const response=new Response(mapping.get(url)??'',{status:mapping.has(url)?200:404}); + if(url===f.manifest.packets[1].result.url)secondServed(); + return response; + }}); + await served;await new Promise(resolve=>setTimeout(resolve,10)); + const host=document.getElementById('packet-cards'),during=host.children.map(child=>child.textContent); + release();await loading; + const slots=host.children.map(child=>descendants(child,n=>n.tagName==='ARTICLE')[0]??child); + return {during,ids,slots:slots.map(node=>/refused/.test(node.className)?'refused':node.id||node.textContent),refused:slots.filter(node=>/refused/.test(node.className))}; +} +test('packet cards render in manifest order when the first packet arrives last',available,async()=>{ + const page=await packetOrderPage(); + assert.deepEqual(page.slots,page.ids); + assert.match(page.during[0],/side24-identity-replay-20260926/,'the held packet keeps its first slot while it verifies'); +}); +test('a refused packet keeps its manifest slot when the other packet arrives later',available,async()=>{ + const page=await packetOrderPage({failSecond:true}); + assert.deepEqual(page.slots,[page.ids[0],'refused']); + assert.deepEqual(page.refused.map(node=>node.id),[page.ids[1]],'the refused card carries its packet id'); + assert.equal(page.refused[0].children[0].textContent,'Packet source'); + assert.ok(page.refused[0].children.some(n=>n.tagName==='P'&&n.className==='error'&&/^Unavailable: .+\. No result inferred\.$/.test(n.textContent))); + assert.ok(page.refused[0].children.some(n=>n.tagName==='P'&&n.textContent==='Packet ID: side24-chart-claude-20260926'),'the refused card names its packet'); +}); diff --git a/tools/public_shop_browser_check.py b/tools/public_shop_browser_check.py index 3558dc6dd..f554201ce 100644 --- a/tools/public_shop_browser_check.py +++ b/tools/public_shop_browser_check.py @@ -2052,6 +2052,26 @@ def refuse_query(route): expect(focused).to_be_focused() require(page.evaluate("Math.abs(document.querySelector('#d3-side24-coefficient').getBoundingClientRect().top)>100"),"Delayed claim completion pulled the reader back") result["steps"].append("keyboard navigation during museum verification keeps focus and reading position") + # Packet cards keep manifest order: hold the first declared packet's RESULT.md until the second packet's + # card (the article, not its "Verifying …" placeholder text) has rendered, then release it. + page.unroute(manifest["claims"][0]["proof"]["url"]) + packets=[packet["id"] for packet in manifest["packets"]];held=[] + require(len(packets)>=2,"The packet-order guard needs at least two declared packets") + page.route(manifest["packets"][0]["result"]["url"],lambda route:held.append(route)) + page.goto(origin+"museum.html",wait_until="domcontentloaded") + deadline=time.monotonic()+45 + while not held and time.monotonic()nodes.map(node=>node.id)") + require(during==[packets[1]],f"Only the second packet should have rendered while the first is held: {during}") + held[0].fulfill(response=held[0].fetch());page.unroute(manifest["packets"][0]["result"]["url"]) + expect(page.locator("#museum-state")).to_contain_text("displayed source bytes verified",timeout=45000) + order=page.eval_on_selector_all("#packet-cards article","nodes=>nodes.map(node=>node.id)") + result["packet_order"]={"held":packets[0],"while_held":during,"rendered":order,"declared":packets} + require(order==packets,f"Packet cards render out of manifest order: {order} != {packets}") + result["steps"].append("packet cards keep manifest order when the first declared packet's bytes arrive last") def main(): From a63fa3f897d268ea00229f7de2d06e1b41a02001 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 9 Oct 2026 19:10:30 +0000 Subject: [PATCH 2/3] Museum packet order: count every refused packet; tighten order guards Follow-up to the previous commit, from review findings. Tests (tests/test_museum_frontend.mjs): - New test: when both packets refuse, both keep their manifest slots and ids and the museum status line counts 2 unavailable views. Before this, folding the packet jobs into one Promise.all job passed every check. - New test: a rendered packet replaces its "Verifying" placeholder (each host settles with one child and no "Verifying" text remains). - The packet-order helper no longer sleeps 10 ms before releasing the first packet. It polls (bounded, 5 s) until the second packet's article is in the page, and the order and refusal tests assert that only the second slot held an article before the release, so the "first packet arrives last" case is exercised under CPU load too. All four packet-order tests and the extended refusal case fail on the museum.mjs before the previous commit. The two new tests fail on the single-job mutation and on a placeholder-append mutation respectively. museum.mjs: the packet jobs are written as jobs.push(...manifest.packets .map(async(packet,i)=>...)) to mirror the claim jobs, and the comment is wrapped to the file's width and says "before any packet fetch". No behaviour change. tools/public_shop_browser_check.py: the packet-order guard now runs first in the museum-reader-intent case, followed by an about:blank load (so the reader-intent load of museum.html#d3-side24-coefficient is a fresh load, not a same-document fragment navigation), so the case screenshot again shows the reader-intent end state with Cite focused. While the first packet is held it requires that the first packet has no article and the second does, rather than an exact one-element list. Not touched: docs/site/README.md, geometry.mjs, test_museum_geometry.mjs, payloads and page text. Other docs/site files change only their ?site-release= key, now 126f4e0b68ae8ef1967d30950ce204527ae9469e9f76d3974cb60bfa31ea63f3. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01S16677s1SGeAJmN3QMz4ip --- docs/site/app.js | 6 ++--- docs/site/brand.css | 2 +- docs/site/cite.html | 4 +-- docs/site/cite.mjs | 2 +- docs/site/conditionals.mjs | 2 +- docs/site/curvature-export.mjs | 2 +- docs/site/dependencies.html | 10 ++++---- docs/site/dependencies.mjs | 2 +- docs/site/explore-models.mjs | 2 +- docs/site/explore.html | 10 ++++---- docs/site/explore.mjs | 12 ++++----- docs/site/formal.html | 6 ++--- docs/site/index.html | 4 +-- docs/site/measure.html | 10 ++++---- docs/site/measure.mjs | 2 +- docs/site/museum.html | 6 ++--- docs/site/museum.mjs | 12 +++++---- docs/site/palette-export.mjs | 4 +-- docs/site/pin-export.mjs | 4 +-- docs/site/reproduce.html | 4 +-- docs/site/research.html | 2 +- docs/site/workspace.html | 6 ++--- tests/test_museum_frontend.mjs | 30 +++++++++++++++++----- tools/public_shop_browser_check.py | 41 +++++++++++++++--------------- 24 files changed, 102 insertions(+), 83 deletions(-) diff --git a/docs/site/app.js b/docs/site/app.js index 83e7618f0..058513939 100644 --- a/docs/site/app.js +++ b/docs/site/app.js @@ -1,6 +1,6 @@ -import {verifiedJSON, verifiedBytes, validateStatus, validateCoefficient, safeSourceURL, hex40, hex64} from './core.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; -import {connectCatalogQuery} from './catalog-query.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; -import {prepareWorkspaceFragment} from './workspace-fragment.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; +import {verifiedJSON, verifiedBytes, validateStatus, validateCoefficient, safeSourceURL, hex40, hex64} from './core.mjs?site-release=126f4e0b68ae8ef1967d30950ce204527ae9469e9f76d3974cb60bfa31ea63f3'; +import {connectCatalogQuery} from './catalog-query.mjs?site-release=126f4e0b68ae8ef1967d30950ce204527ae9469e9f76d3974cb60bfa31ea63f3'; +import {prepareWorkspaceFragment} from './workspace-fragment.mjs?site-release=126f4e0b68ae8ef1967d30950ce204527ae9469e9f76d3974cb60bfa31ea63f3'; const finishFragment=prepareWorkspaceFragment(); const el=id=>document.getElementById(id); const node=(tag,text,cls)=>{const n=document.createElement(tag);if(text!==undefined)n.textContent=text;if(cls)n.className=cls;return n;}; diff --git a/docs/site/brand.css b/docs/site/brand.css index e3285642e..e6b886aaf 100644 --- a/docs/site/brand.css +++ b/docs/site/brand.css @@ -60,7 +60,7 @@ body > header { color: var(--ul-text); font-weight: 750; letter-spacing: .13em; position: relative; padding-left: 57px; min-height: 44px; line-height: 1.65; } -.brand::before { content: ""; position: absolute; left: 0; top: 1px; width: 42px; height: 42px; background: url("brand/mark.svg?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b") center/contain no-repeat; } +.brand::before { content: ""; position: absolute; left: 0; top: 1px; width: 42px; height: 42px; background: url("brand/mark.svg?site-release=126f4e0b68ae8ef1967d30950ce204527ae9469e9f76d3974cb60bfa31ea63f3") center/contain no-repeat; } .brand span { color: var(--ul-muted); font-size: 12px; letter-spacing: .16em; text-transform: uppercase; } nav { gap: 12px 24px; flex-wrap: wrap; } nav a { color: var(--ul-text); min-height: 44px; display: inline-flex; align-items: center; font-size: 13px; padding: 0 6px; margin: 0 -6px; } diff --git a/docs/site/cite.html b/docs/site/cite.html index fbd5e330f..c87d904f0 100644 --- a/docs/site/cite.html +++ b/docs/site/cite.html @@ -4,8 +4,8 @@ Cite · Universal Law - - + +
UNIVERSAL LAW MATHEMATICS IN THE OPEN
diff --git a/docs/site/cite.mjs b/docs/site/cite.mjs index 92b35664c..240407a76 100644 --- a/docs/site/cite.mjs +++ b/docs/site/cite.mjs @@ -1,4 +1,4 @@ -import {wireReferenceForm} from './source-reference.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; +import {wireReferenceForm} from './source-reference.mjs?site-release=126f4e0b68ae8ef1967d30950ce204527ae9469e9f76d3974cb60bfa31ea63f3'; const byId = id => document.getElementById(id); wireReferenceForm({form:byId('reference-form'), repository:byId('reference-repository'), commit:byId('reference-commit'), path:byId('reference-path'), sha256:byId('reference-sha256'), output:byId('reference-output'), json:byId('reference-json'), bibtex:byId('reference-bibtex'), link:byId('reference-link'), status:byId('reference-status'), actions:byId('reference-actions'), copyText:byId('reference-copy'), copyJSON:byId('reference-copy-json'), copyBibTeX:byId('reference-copy-bibtex'), share:byId('reference-share'), clipboard:globalThis.navigator?.clipboard, location:globalThis.location, history:globalThis.history, events:globalThis.window}); byId('reference-builder').hidden = false; diff --git a/docs/site/conditionals.mjs b/docs/site/conditionals.mjs index 656248a62..ff41ab1cd 100644 --- a/docs/site/conditionals.mjs +++ b/docs/site/conditionals.mjs @@ -93,7 +93,7 @@ export function renderProjection(document,host,data,source){ el(document,'p','Generated from verified source bytes. This graph is not formal verification, a proof review, or a scientific-status register.')); host.replaceChildren(article); } -const services=()=>import('./museum.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'); +const services=()=>import('./museum.mjs?site-release=126f4e0b68ae8ef1967d30950ce204527ae9469e9f76d3974cb60bfa31ea63f3'); // The museum module already verifies config → manifest → index/status once per // page fetch and caches every pinned byte request. This route consumes that same // verified startup, so it adds only the audited-descriptor check and projection. diff --git a/docs/site/curvature-export.mjs b/docs/site/curvature-export.mjs index a21f4b9d4..278e3bb1e 100644 --- a/docs/site/curvature-export.mjs +++ b/docs/site/curvature-export.mjs @@ -1,6 +1,6 @@ // Bounded, local export of the Peaks and saddles teaching diagram. No source fetch // or proof verification occurs here; the identity below is recorded provenance. -import { coneModel } from './explore-models.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; +import { coneModel } from './explore-models.mjs?site-release=126f4e0b68ae8ef1967d30950ce204527ae9469e9f76d3974cb60bfa31ea63f3'; const SVG_NS = 'http://www.w3.org/2000/svg'; const PUBLIC_EXPLORE = 'https://d6g8k5htny-coder.github.io/main/site/explore.html'; diff --git a/docs/site/dependencies.html b/docs/site/dependencies.html index 469cafe8f..8e5418775 100644 --- a/docs/site/dependencies.html +++ b/docs/site/dependencies.html @@ -7,11 +7,11 @@ Claim dependencies · Universal Law - - - - - + + + + + diff --git a/docs/site/dependencies.mjs b/docs/site/dependencies.mjs index d2fc74197..acfa987d9 100644 --- a/docs/site/dependencies.mjs +++ b/docs/site/dependencies.mjs @@ -12,7 +12,7 @@ import { recordedContextRows, recordedReviewText, unresolvedTargets, -} from './dependency-model.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; +} from './dependency-model.mjs?site-release=126f4e0b68ae8ef1967d30950ce204527ae9469e9f76d3974cb60bfa31ea63f3'; const byId = id => document.getElementById(id); diff --git a/docs/site/explore-models.mjs b/docs/site/explore-models.mjs index 156a74c7c..f2800bfec 100644 --- a/docs/site/explore-models.mjs +++ b/docs/site/explore-models.mjs @@ -1,6 +1,6 @@ // NON-CERTIFYING educational arithmetic. These functions illustrate source // definitions; they do not simulate a field or estimate a theorem's constants. -import { annulusModel, p15Model, remoteModel } from './geometry.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; +import { annulusModel, p15Model, remoteModel } from './geometry.mjs?site-release=126f4e0b68ae8ef1967d30950ce204527ae9469e9f76d3974cb60bfa31ea63f3'; function finite(value, name) { if (!Number.isFinite(value)) throw new RangeError(`${name} must be finite`); diff --git a/docs/site/explore.html b/docs/site/explore.html index 78391e792..858b7b5c4 100644 --- a/docs/site/explore.html +++ b/docs/site/explore.html @@ -7,11 +7,11 @@ Explore the mathematics · Universal Law - - - - - + + + + + diff --git a/docs/site/explore.mjs b/docs/site/explore.mjs index 007939169..424a71cd2 100644 --- a/docs/site/explore.mjs +++ b/docs/site/explore.mjs @@ -1,10 +1,10 @@ -import { coneModel, pinModel, paletteModel, applyCurvaturePreset } from './explore-models.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; -import { readExploreState, exploreStateURL } from './explore-state.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; +import { coneModel, pinModel, paletteModel, applyCurvaturePreset } from './explore-models.mjs?site-release=126f4e0b68ae8ef1967d30950ce204527ae9469e9f76d3974cb60bfa31ea63f3'; +import { readExploreState, exploreStateURL } from './explore-state.mjs?site-release=126f4e0b68ae8ef1967d30950ce204527ae9469e9f76d3974cb60bfa31ea63f3'; -import { captureCurvatureDiagram, serializeCurvatureSVG, createCitationController } from './curvature-export.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; -import { captureTeachingDiagram, createFigureCitationController } from './teaching-export.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; -import { serializePinSVG, pinFigureCitation } from './pin-export.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; -import { serializePaletteSVG, paletteFigureCitation } from './palette-export.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; +import { captureCurvatureDiagram, serializeCurvatureSVG, createCitationController } from './curvature-export.mjs?site-release=126f4e0b68ae8ef1967d30950ce204527ae9469e9f76d3974cb60bfa31ea63f3'; +import { captureTeachingDiagram, createFigureCitationController } from './teaching-export.mjs?site-release=126f4e0b68ae8ef1967d30950ce204527ae9469e9f76d3974cb60bfa31ea63f3'; +import { serializePinSVG, pinFigureCitation } from './pin-export.mjs?site-release=126f4e0b68ae8ef1967d30950ce204527ae9469e9f76d3974cb60bfa31ea63f3'; +import { serializePaletteSVG, paletteFigureCitation } from './palette-export.mjs?site-release=126f4e0b68ae8ef1967d30950ce204527ae9469e9f76d3974cb60bfa31ea63f3'; const ns = 'http://www.w3.org/2000/svg'; const byId = id => document.getElementById(id); diff --git a/docs/site/formal.html b/docs/site/formal.html index e76487430..e701eb1f1 100644 --- a/docs/site/formal.html +++ b/docs/site/formal.html @@ -5,9 +5,9 @@ Formal verification · Universal Law - - - + + + diff --git a/docs/site/index.html b/docs/site/index.html index f3c6b13bb..242e11926 100644 --- a/docs/site/index.html +++ b/docs/site/index.html @@ -4,8 +4,8 @@ The shape of randomness · Universal Law - - + +
UNIVERSAL LAW MATHEMATICS IN THE OPEN
diff --git a/docs/site/measure.html b/docs/site/measure.html index d6cc7594a..c48b8f87a 100644 --- a/docs/site/measure.html +++ b/docs/site/measure.html @@ -7,11 +7,11 @@ From counts to density · Universal Law - - - - - + + + + + diff --git a/docs/site/measure.mjs b/docs/site/measure.mjs index df32b66b4..f8cf9c91f 100644 --- a/docs/site/measure.mjs +++ b/docs/site/measure.mjs @@ -1,4 +1,4 @@ -import { measurementModel, parseMeasurementInputs } from './measurement-model.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; +import { measurementModel, parseMeasurementInputs } from './measurement-model.mjs?site-release=126f4e0b68ae8ef1967d30950ce204527ae9469e9f76d3974cb60bfa31ea63f3'; const byId = id => document.getElementById(id); const fields = Object.fromEntries(['count', 'realizations', 'lower', 'upper'].map(name => [name, byId(`measure-${name}`)])); diff --git a/docs/site/museum.html b/docs/site/museum.html index 7676cdbc0..07692a6aa 100644 --- a/docs/site/museum.html +++ b/docs/site/museum.html @@ -5,11 +5,11 @@ Source records & exhibits · Universal Law - + - - + + diff --git a/docs/site/museum.mjs b/docs/site/museum.mjs index 46b2a562f..bc5b2ebd8 100644 --- a/docs/site/museum.mjs +++ b/docs/site/museum.mjs @@ -1,5 +1,5 @@ -import {renderSourceQuote} from './source-quote.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; -import {verifiedBytes, hex40, hex64} from './core.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; +import {renderSourceQuote} from './source-quote.mjs?site-release=126f4e0b68ae8ef1967d30950ce204527ae9469e9f76d3974cb60bfa31ea63f3'; +import {verifiedBytes, hex40, hex64} from './core.mjs?site-release=126f4e0b68ae8ef1967d30950ce204527ae9469e9f76d3974cb60bfa31ea63f3'; export const DISCLAIMER='This canvas explains the pinned source. It is not a proof and does not change status.'; const REVIEWED_IDS=['d2-lifetime-remainder','d3-side24-coefficient','d4-fixed-remote-rn','d5-all-height-annulus','d5-height-window-annulus','d5-two-scale','d5-inner-belt-density','d5-fixed-transverse','cumulative-transfer-correction','p15-demand-one-counterexample','d6-p15-full-price']; @@ -264,7 +264,7 @@ function prepareClaimFragment(document,window,currentHash) { return true; }; } -export async function startMuseum({document=globalThis.document,window=globalThis.window,fetcher=globalThis.fetch,search=globalThis.location?.search||'',geometryLoader=()=>import('./geometry.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'),currentHash=()=>globalThis.location?.hash||''}={}){ +export async function startMuseum({document=globalThis.document,window=globalThis.window,fetcher=globalThis.fetch,search=globalThis.location?.search||'',geometryLoader=()=>import('./geometry.mjs?site-release=126f4e0b68ae8ef1967d30950ce204527ae9469e9f76d3974cb60bfa31ea63f3'),currentHash=()=>globalThis.location?.hash||''}={}){ const ids=['museum-state','claim-cards','lifetime-fixture','packet-cards','active-exhibit']; const containers=Object.fromEntries(ids.map(id=>{const node=document.getElementById(id);if(!node)throw Error(`Missing museum container: ${id}`);return [id,node];})); const finishFragment=prepareClaimFragment(document,window,currentHash); @@ -276,9 +276,11 @@ export async function startMuseum({document=globalThis.document,window=globalThi const jobs=manifest.claims.map(async(claim,i)=>{try{await verifyClaim(claim,cachedFetch);placeholders[i].replaceChildren(renderClaim(document,claim,REVIEWED_IDS.includes(claim.id)?manifest.index_source:manifest.status_source));}catch(error){const unavailable=refusal(document,claim.title,error);unavailable.id=claim.id;placeholders[i].replaceChildren(unavailable);return false;}return true;}); jobs.push((async()=>{try{containers['lifetime-fixture'].replaceChildren(await renderLifetime(document,manifest.exhibits.lifetime,cachedFetch));return true;}catch(error){containers['lifetime-fixture'].replaceChildren(refusal(document,'D2 lifetime fixture',error));return false;}})()); if(!manifest.packets.length)containers['packet-cards'].append(element(document,'p','No packet appears in this pinned projection.')); - // One host per packet, appended in manifest order before any fetch, as for the claim cards: a card fills its own slot whenever its bytes arrive, and a refusal names its packet. + // One host per packet, appended in manifest order before any packet fetch, as for + // the claim cards: a card fills its own slot whenever its bytes arrive, and a + // refusal names its packet. const packetHosts=manifest.packets.map(packet=>{const host=element(document,'div');host.append(element(document,'p',`Verifying ${packet.id}…`));containers['packet-cards'].append(host);return host;}); - manifest.packets.forEach((packet,i)=>jobs.push((async()=>{try{packetHosts[i].replaceChildren(await renderPacket(document,packet,cachedFetch));return true;}catch(error){const unavailable=refusal(document,'Packet source',error);unavailable.id=packet.id;unavailable.append(element(document,'p',`Packet ID: ${packet.id}`));packetHosts[i].replaceChildren(unavailable);return false;}})())); + jobs.push(...manifest.packets.map(async(packet,i)=>{try{packetHosts[i].replaceChildren(await renderPacket(document,packet,cachedFetch));}catch(error){const unavailable=refusal(document,'Packet source',error);unavailable.id=packet.id;unavailable.append(element(document,'p',`Packet ID: ${packet.id}`));packetHosts[i].replaceChildren(unavailable);return false;}return true;})); const kind=new URLSearchParams(search).get('view'); if(kind){ if(!Object.hasOwn(EXHIBITS,kind)){containers['active-exhibit'].append(refusal(document,'Unknown exhibit',Error('Select one of the four declared source illustrations')));jobs.push(false);} diff --git a/docs/site/palette-export.mjs b/docs/site/palette-export.mjs index 8bbc58e52..aa71c7535 100644 --- a/docs/site/palette-export.mjs +++ b/docs/site/palette-export.mjs @@ -1,7 +1,7 @@ // Bounded teaching export of Explore's current finite P15 diagram. Source // custody is provenance; this module performs no mathematical verification. -import { paletteModel } from './explore-models.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; -import { serializeTeachingSVG } from './teaching-export.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; +import { paletteModel } from './explore-models.mjs?site-release=126f4e0b68ae8ef1967d30950ce204527ae9469e9f76d3974cb60bfa31ea63f3'; +import { serializeTeachingSVG } from './teaching-export.mjs?site-release=126f4e0b68ae8ef1967d30950ce204527ae9469e9f76d3974cb60bfa31ea63f3'; const PUBLIC_EXPLORE = 'https://d6g8k5htny-coder.github.io/main/site/explore.html'; const SOURCE = Object.freeze({ diff --git a/docs/site/pin-export.mjs b/docs/site/pin-export.mjs index edcfed45c..cad66bd87 100644 --- a/docs/site/pin-export.mjs +++ b/docs/site/pin-export.mjs @@ -1,7 +1,7 @@ // Bounded export of the CURRENT displayed Approach-points teaching diagram. // Pinned source identities are provenance, not proof acceptance or verification. -import { pinModel } from './explore-models.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; -import { serializeTeachingSVG } from './teaching-export.mjs?site-release=f9946485f02c1994ebf2933fff8a7471a4b55ea22fb59b5b146e9da3c10ae32b'; +import { pinModel } from './explore-models.mjs?site-release=126f4e0b68ae8ef1967d30950ce204527ae9469e9f76d3974cb60bfa31ea63f3'; +import { serializeTeachingSVG } from './teaching-export.mjs?site-release=126f4e0b68ae8ef1967d30950ce204527ae9469e9f76d3974cb60bfa31ea63f3'; const PUBLIC_EXPLORE='https://d6g8k5htny-coder.github.io/main/site/explore.html'; const COMMIT='d6628da09384728992dcbe6e921cc28ba85aebb0'; diff --git a/docs/site/reproduce.html b/docs/site/reproduce.html index 3c2a49a79..cbbd503c1 100644 --- a/docs/site/reproduce.html +++ b/docs/site/reproduce.html @@ -4,9 +4,9 @@ Reproduce · Universal Law - + - +
UNIVERSAL LAW MATHEMATICS IN THE OPEN
diff --git a/docs/site/research.html b/docs/site/research.html index 616f54dec..37e766e2b 100644 --- a/docs/site/research.html +++ b/docs/site/research.html @@ -4,7 +4,7 @@ Research · Universal Law - +
UNIVERSAL LAW MATHEMATICS IN THE OPEN
diff --git a/docs/site/workspace.html b/docs/site/workspace.html index f60c08487..f8c1892f1 100644 --- a/docs/site/workspace.html +++ b/docs/site/workspace.html @@ -5,9 +5,9 @@ Library and research workspace · Universal Law - - - + + + diff --git a/tests/test_museum_frontend.mjs b/tests/test_museum_frontend.mjs index 920ff36f5..a21da807b 100644 --- a/tests/test_museum_frontend.mjs +++ b/tests/test_museum_frontend.mjs @@ -382,13 +382,13 @@ import "./test_source_quote.mjs"; // Packet cards keep manifest order whatever order their RESULT.md bytes arrive in: the first // declared packet is held until the second has rendered, and a refusal keeps its own slot. -async function packetOrderPage({failSecond=false}={}){ +async function packetOrderPage({failFirst=false,failSecond=false}={}){ const f=fixture(),ids=['side24-identity-replay-20260926','side24-chart-claude-20260926'],commits=['71400b94f6cb354a8cf7aba73ffede2138a64efa','a12c178c0f857a130cf434e9efd44233a038195b']; const text='Scientific effect: **NONE**\nReview status: **REVIEW_REQUIRED**\n'; const source=(i,name)=>{const path=`incoming/${ids[i]}/${name}`;return {...pin(path,text,commits[i]),repository:'d6g8k5htny-coder/main',url:`https://raw.githubusercontent.com/d6g8k5htny-coder/main/${commits[i]}/${path}`,html_url:`https://github.com/d6g8k5htny-coder/main/blob/${commits[i]}/${path}`};}; f.manifest.packets=ids.map((id,i)=>({id,issue:i?141:null,result:source(i,'RESULT.md'),identity:source(i,'IDENTITY.json'),output:source(i,'output.json'),scientific_effect:'NONE',review_status:'REVIEW_REQUIRED'})); const raw=JSON.stringify(f.manifest),document=documentFromHTML(); - const mapping=new Map([['config.json',JSON.stringify({museum_json:{url:'museum.json',bytes:Buffer.byteLength(raw),sha256:digest(raw)}})],['museum.json',raw],[f.manifest.index_source.url,f.index],[f.manifest.status_source.url,f.status],[f.manifest.claims[0].proof.url,'Pinned proof.'],[f.manifest.claims[11].review.url,'Pinned reconciliation.'],[f.manifest.packets[0].result.url,text],[f.manifest.packets[1].result.url,failSecond?text+'changed':text]]); + const mapping=new Map([['config.json',JSON.stringify({museum_json:{url:'museum.json',bytes:Buffer.byteLength(raw),sha256:digest(raw)}})],['museum.json',raw],[f.manifest.index_source.url,f.index],[f.manifest.status_source.url,f.status],[f.manifest.claims[0].proof.url,'Pinned proof.'],[f.manifest.claims[11].review.url,'Pinned reconciliation.'],[f.manifest.packets[0].result.url,failFirst?text+'changed':text],[f.manifest.packets[1].result.url,failSecond?text+'changed':text]]); let release,secondServed;const gate=new Promise(resolve=>release=resolve),served=new Promise(resolve=>secondServed=resolve); const loading=museum.startMuseum({document,search:'',fetcher:async url=>{ url=String(url); @@ -397,22 +397,38 @@ async function packetOrderPage({failSecond=false}={}){ if(url===f.manifest.packets[1].result.url)secondServed(); return response; }}); - await served;await new Promise(resolve=>setTimeout(resolve,10)); - const host=document.getElementById('packet-cards'),during=host.children.map(child=>child.textContent); + // Release the first packet only once the second packet's card (rendered or refused) is in the page. + const host=document.getElementById('packet-cards'),deadline=Date.now()+5000; + await served;while(!descendants(host,n=>n.tagName==='ARTICLE'&&n.id===ids[1]).length&&Date.now()setTimeout(resolve,5)); + const article=child=>descendants(child,n=>n.tagName==='ARTICLE')[0]??(child.tagName==='ARTICLE'?child:undefined); + const during=host.children.map(child=>child.textContent),duringIds=host.children.map(child=>article(child)?.id); release();await loading; - const slots=host.children.map(child=>descendants(child,n=>n.tagName==='ARTICLE')[0]??child); - return {during,ids,slots:slots.map(node=>/refused/.test(node.className)?'refused':node.id||node.textContent),refused:slots.filter(node=>/refused/.test(node.className))}; + const slots=host.children.map(child=>article(child)??child); + return {state:document.getElementById('museum-state').textContent,settled:host.children.map(child=>child.children.length),leftover:/Verifying/.test(host.textContent),during,duringIds,ids,slots:slots.map(node=>/refused/.test(node.className)?'refused':node.id||node.textContent),refused:slots.filter(node=>/refused/.test(node.className))}; } test('packet cards render in manifest order when the first packet arrives last',available,async()=>{ const page=await packetOrderPage(); - assert.deepEqual(page.slots,page.ids); + assert.deepEqual(page.duringIds,[undefined,page.ids[1]],'the second packet rendered before the first was released'); assert.match(page.during[0],/side24-identity-replay-20260926/,'the held packet keeps its first slot while it verifies'); + assert.deepEqual(page.slots,page.ids); }); test('a refused packet keeps its manifest slot when the other packet arrives later',available,async()=>{ const page=await packetOrderPage({failSecond:true}); + assert.deepEqual(page.duringIds,[undefined,page.ids[1]],'the second packet was refused before the first was released'); assert.deepEqual(page.slots,[page.ids[0],'refused']); assert.deepEqual(page.refused.map(node=>node.id),[page.ids[1]],'the refused card carries its packet id'); assert.equal(page.refused[0].children[0].textContent,'Packet source'); assert.ok(page.refused[0].children.some(n=>n.tagName==='P'&&n.className==='error'&&/^Unavailable: .+\. No result inferred\.$/.test(n.textContent))); assert.ok(page.refused[0].children.some(n=>n.tagName==='P'&&n.textContent==='Packet ID: side24-chart-claude-20260926'),'the refused card names its packet'); }); +test('every refused packet is counted in the museum status line',available,async()=>{ + const page=await packetOrderPage({failFirst:true,failSecond:true}); + assert.deepEqual(page.slots,['refused','refused']); + assert.deepEqual(page.refused.map(node=>node.id),page.ids); + assert.equal(page.state,countedViews(2)); +}); +test('a rendered packet replaces its Verifying placeholder',available,async()=>{ + const page=await packetOrderPage(); + assert.deepEqual(page.settled,[1,1]); + assert.equal(page.leftover,false); +}); diff --git a/tools/public_shop_browser_check.py b/tools/public_shop_browser_check.py index f554201ce..4ed9dd54c 100644 --- a/tools/public_shop_browser_check.py +++ b/tools/public_shop_browser_check.py @@ -2034,6 +2034,27 @@ def refuse_query(route): result["steps"].append("query 503 ends loading without erasing verified import information") else: manifest=json.loads((ROOT/"docs/site/museum.json").read_text()) + # Packet cards keep manifest order: hold the first declared packet's RESULT.md until the second packet's + # card (the article, not its "Verifying …" placeholder text) has rendered, then release it. This runs + # before the reader-intent flow so that the case screenshot shows that flow's reading position. + packets=[packet["id"] for packet in manifest["packets"]];held=[] + require(len(packets)>=2,"The packet-order guard needs at least two declared packets") + page.route(manifest["packets"][0]["result"]["url"],lambda route:held.append(route)) + page.goto(origin+"museum.html",wait_until="domcontentloaded") + deadline=time.monotonic()+45 + while not held and time.monotonic()nodes.map(node=>node.id)") + require(packets[0] not in during and packets[1] in during,f"The second packet should have rendered while the first is held: {during}") + held[0].fulfill(response=held[0].fetch());page.unroute(manifest["packets"][0]["result"]["url"]) + expect(page.locator("#museum-state")).to_contain_text("displayed source bytes verified",timeout=45000) + order=page.eval_on_selector_all("#packet-cards article","nodes=>nodes.map(node=>node.id)") + result["packet_order"]={"held":packets[0],"while_held":during,"rendered":order,"declared":packets} + require(order==packets,f"Packet cards render out of manifest order: {order} != {packets}") + result["steps"].append("packet cards keep manifest order when the first declared packet's bytes arrive last") + page.goto("about:blank") # museum.html → museum.html#… would be a same-document fragment navigation, not a fresh load. held=[] page.route(manifest["claims"][0]["proof"]["url"],lambda route:held.append(route)) page.goto(origin+"museum.html#d3-side24-coefficient",wait_until="domcontentloaded") @@ -2052,26 +2073,6 @@ def refuse_query(route): expect(focused).to_be_focused() require(page.evaluate("Math.abs(document.querySelector('#d3-side24-coefficient').getBoundingClientRect().top)>100"),"Delayed claim completion pulled the reader back") result["steps"].append("keyboard navigation during museum verification keeps focus and reading position") - # Packet cards keep manifest order: hold the first declared packet's RESULT.md until the second packet's - # card (the article, not its "Verifying …" placeholder text) has rendered, then release it. - page.unroute(manifest["claims"][0]["proof"]["url"]) - packets=[packet["id"] for packet in manifest["packets"]];held=[] - require(len(packets)>=2,"The packet-order guard needs at least two declared packets") - page.route(manifest["packets"][0]["result"]["url"],lambda route:held.append(route)) - page.goto(origin+"museum.html",wait_until="domcontentloaded") - deadline=time.monotonic()+45 - while not held and time.monotonic()nodes.map(node=>node.id)") - require(during==[packets[1]],f"Only the second packet should have rendered while the first is held: {during}") - held[0].fulfill(response=held[0].fetch());page.unroute(manifest["packets"][0]["result"]["url"]) - expect(page.locator("#museum-state")).to_contain_text("displayed source bytes verified",timeout=45000) - order=page.eval_on_selector_all("#packet-cards article","nodes=>nodes.map(node=>node.id)") - result["packet_order"]={"held":packets[0],"while_held":during,"rendered":order,"declared":packets} - require(order==packets,f"Packet cards render out of manifest order: {order} != {packets}") - result["steps"].append("packet cards keep manifest order when the first declared packet's bytes arrive last") def main(): From 662c4f7b8e86996405f36ca73faafebf01445fd4 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 9 Oct 2026 19:48:17 +0000 Subject: [PATCH 3/3] Museum packet order: pin the placeholder and refusal slots in tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three test-strength gaps from the last local verification round, each confirmed by a mutation that passed before this commit and fails after it: - "every refused packet is counted" now also requires each refusal to replace its "Verifying …" placeholder. Appending the refusal next to the placeholder passed before. - "a coherent old browser cache cannot hide the newly pinned packet" (run with MUSEUM_FIXTURE by tools/museum_check.py) matched the chart packet's id in the container text. The placeholder and the new "Packet ID:" line carry that text too, so a refused chart card passed it. It now requires both packet cards to be rendered, non-refused articles with their ids. - The order test now requires the held slot to be a plain div holding a plain paragraph: no card class, role or live region. A placeholder with role=status passed before. tools/public_shop_browser_check.py: the order guard's failure message also names any refused card, and its comment notes museum.mjs's 20 s request deadline. No docs/site file changes, so the release key is unchanged. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01S16677s1SGeAJmN3QMz4ip --- tests/test_museum_frontend.mjs | 10 ++++++++-- tools/public_shop_browser_check.py | 6 ++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/tests/test_museum_frontend.mjs b/tests/test_museum_frontend.mjs index a21da807b..3d81a22fb 100644 --- a/tests/test_museum_frontend.mjs +++ b/tests/test_museum_frontend.mjs @@ -293,7 +293,8 @@ test('a coherent old browser cache cannot hide the newly pinned packet',{skip:!m return new Response(Buffer.from(fixtures[String(url)],'base64')); }}); assert.equal(document.getElementById('packet-cards').children.length,2); - assert.match(document.getElementById('packet-cards').textContent,/side24-chart-claude-20260926/); + // Match rendered cards, not text: a Verifying placeholder or a refusal also names the packet. + assert.deepEqual(descendants(document.getElementById('packet-cards'),n=>n.tagName==='ARTICLE').map(n=>[n.id,/refused/.test(n.className)]),[['side24-identity-replay-20260926',false],['side24-chart-claude-20260926',false]]); assert.deepEqual(calls.slice(0,2),[{url:'config.json',cache:'no-store'},{url:'museum.json',cache:'no-store'}]); assert.ok(calls.slice(2).every(call=>call.cache===undefined),'Pinned remote source requests retain their cache policy'); }); @@ -402,14 +403,17 @@ async function packetOrderPage({failFirst=false,failSecond=false}={}){ await served;while(!descendants(host,n=>n.tagName==='ARTICLE'&&n.id===ids[1]).length&&Date.now()setTimeout(resolve,5)); const article=child=>descendants(child,n=>n.tagName==='ARTICLE')[0]??(child.tagName==='ARTICLE'?child:undefined); const during=host.children.map(child=>child.textContent),duringIds=host.children.map(child=>article(child)?.id); + const flagged=n=>isLive(n)||(typeof n.getAttribute==='function'&&n.getAttribute('role')!==null); + const placeholder=[host.children[0],...host.children[0].children].map(n=>({tag:n.tagName,className:n.className??'',flagged:flagged(n)})); release();await loading; const slots=host.children.map(child=>article(child)??child); - return {state:document.getElementById('museum-state').textContent,settled:host.children.map(child=>child.children.length),leftover:/Verifying/.test(host.textContent),during,duringIds,ids,slots:slots.map(node=>/refused/.test(node.className)?'refused':node.id||node.textContent),refused:slots.filter(node=>/refused/.test(node.className))}; + return {placeholder,state:document.getElementById('museum-state').textContent,settled:host.children.map(child=>child.children.length),leftover:/Verifying/.test(host.textContent),during,duringIds,ids,slots:slots.map(node=>/refused/.test(node.className)?'refused':node.id||node.textContent),refused:slots.filter(node=>/refused/.test(node.className))}; } test('packet cards render in manifest order when the first packet arrives last',available,async()=>{ const page=await packetOrderPage(); assert.deepEqual(page.duringIds,[undefined,page.ids[1]],'the second packet rendered before the first was released'); assert.match(page.during[0],/side24-identity-replay-20260926/,'the held packet keeps its first slot while it verifies'); + assert.deepEqual(page.placeholder,[{tag:'DIV',className:'',flagged:false},{tag:'P',className:'',flagged:false}],'the placeholder is plain text: no card class, role or live region'); assert.deepEqual(page.slots,page.ids); }); test('a refused packet keeps its manifest slot when the other packet arrives later',available,async()=>{ @@ -426,6 +430,8 @@ test('every refused packet is counted in the museum status line',available,async assert.deepEqual(page.slots,['refused','refused']); assert.deepEqual(page.refused.map(node=>node.id),page.ids); assert.equal(page.state,countedViews(2)); + assert.deepEqual(page.settled,[1,1],'each refusal replaces its Verifying placeholder'); + assert.equal(page.leftover,false); }); test('a rendered packet replaces its Verifying placeholder',available,async()=>{ const page=await packetOrderPage(); diff --git a/tools/public_shop_browser_check.py b/tools/public_shop_browser_check.py index 4ed9dd54c..6ef072be2 100644 --- a/tools/public_shop_browser_check.py +++ b/tools/public_shop_browser_check.py @@ -2036,7 +2036,8 @@ def refuse_query(route): manifest=json.loads((ROOT/"docs/site/museum.json").read_text()) # Packet cards keep manifest order: hold the first declared packet's RESULT.md until the second packet's # card (the article, not its "Verifying …" placeholder text) has rendered, then release it. This runs - # before the reader-intent flow so that the case screenshot shows that flow's reading position. + # before the reader-intent flow so that the case screenshot shows that flow's reading position. museum.mjs + # gives each source request 20 s, so a held packet that waits longer is refused, not reordered. packets=[packet["id"] for packet in manifest["packets"]];held=[] require(len(packets)>=2,"The packet-order guard needs at least two declared packets") page.route(manifest["packets"][0]["result"]["url"],lambda route:held.append(route)) @@ -2047,7 +2048,8 @@ def refuse_query(route): require(len(held)==1,"The first packet's RESULT.md was not intercepted") expect(page.locator(f"#packet-cards article#{packets[1]}")).to_be_attached(timeout=45000) during=page.eval_on_selector_all("#packet-cards article","nodes=>nodes.map(node=>node.id)") - require(packets[0] not in during and packets[1] in during,f"The second packet should have rendered while the first is held: {during}") + refused=page.eval_on_selector_all("#packet-cards article.refused","nodes=>nodes.map(node=>node.id)") + require(packets[0] not in during and packets[1] in during,f"The second packet should have rendered while the first is held: {during} (refused: {refused})") held[0].fulfill(response=held[0].fetch());page.unroute(manifest["packets"][0]["result"]["url"]) expect(page.locator("#museum-state")).to_contain_text("displayed source bytes verified",timeout=45000) order=page.eval_on_selector_all("#packet-cards article","nodes=>nodes.map(node=>node.id)")