Chain scene 2.0.4: the box's height follows the lanes (onSize, autoHeight, the lane geometry in stats) (7 October 2026, 16:3x UK)

the project lead's /live screenshot: a fixed tall box with the lanes in its top third and dead space under them. The renderer now knows
the height it wants: top padding + axis padding + lanes shown (at most maxLanes 7, narrowLanes 4 on a phone, never under 2)
times laneHeight (46 px, 40 on a phone, 26 compact; the mount option laneHeight overrides). It reports it through
onSize(heightPx, {lanes, laneHeight, narrow, compact}) whenever it changes and through getWantedHeight(); stats() carries
laneHeight, padT, padB, capacity, wantedHeight and autoHeight, so a page that sizes its own box reads no constants. With
autoHeight (on by default when the host set no CSS height on the canvas, which is read before the first size(); forced either
way by the option; never in compact mode, the app's card keeps its own height) the renderer sets canvas.style.height itself
and lets every lane through instead of fitting the lanes to the box. Every current host sets a height (/live 420, the hero
500, the app's card 220 and its Inspect view 420), so the frames are unchanged: the parity test on build-2 stayed equal on
every comparison. The site lane switches /live and the home fold to the hook.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-07 13:58:33 +00:00
parent 7c79bee818
commit 1ae09fb5a9
3 changed files with 47 additions and 9 deletions

View file

@ -5,7 +5,7 @@ are ONE renderer fed ONE JSON shape. This folder is the source; everything else
| File | What it is | Copies | | File | What it is | Copies |
|---|---|---| |---|---|---|
| `live-dag.js` | EMBER 02 `IgneumDag` 2.0.3: lanes per vote key, parent curves, the selected chain, checkpoint bands, proof glow | `site/live-dag.js`, `app/igneum-app/ui/live-dag.js` | | `live-dag.js` | EMBER 02 `IgneumDag` 2.0.4: lanes per vote key, parent curves, the selected chain, checkpoint bands, proof glow; the box height follows the lanes (`onSize`, `autoHeight`) | `site/live-dag.js`, `app/igneum-app/ui/live-dag.js` |
| `proof-core.js` | `IgneumProof` 2.0.0: the selected block's shards | `site/proof-core.js`, `app/igneum-app/ui/proof-core.js` | | `proof-core.js` | `IgneumProof` 2.0.0: the selected block's shards | `site/proof-core.js`, `app/igneum-app/ui/proof-core.js` |
| `tokens.css` | The fourteen palette tokens the two scripts read from `:root`, dark and light, the brand package's values | the `scene-tokens` block in `site/site.css` and `app/igneum-app/ui/app.css` | | `tokens.css` | The fourteen palette tokens the two scripts read from `:root`, dark and light, the brand package's values | the `scene-tokens` block in `site/site.css` and `app/igneum-app/ui/app.css` |
| `feed-contract.md`, `feed-contract.json` | The feed shape, in words and as key lists | read by `tools/scene/feed-contract.mjs` and the app's `src/live.rs` test | | `feed-contract.md`, `feed-contract.json` | The feed shape, in words and as key lists | read by `tools/scene/feed-contract.mjs` and the app's `src/live.rs` test |
@ -25,6 +25,14 @@ option names this machine's vote keys, so its own blocks glow and its lane reads
picture, never a second renderer, never a rewritten feed. The phone rule (30 s window, four lanes, bigger nodes) keys on the picture, never a second renderer, never a rewritten feed. The phone rule (30 s window, four lanes, bigger nodes) keys on the
viewport width under 720 px, not on the canvas width: a 640 px hero on a laptop is not a phone. viewport width under 720 px, not on the canvas width: a 640 px hero on a laptop is not a phone.
The box's height follows the lanes (2.0.4): wanted height = top padding + axis padding + lanes shown times `laneHeight`
(46 px, 40 on a phone, 26 compact; at most `maxLanes` 7, `narrowLanes` 4 on a phone, never under 2). The renderer calls
`onSize(heightPx, {lanes, laneHeight, narrow, compact})` whenever that changes and exposes `getWantedHeight()`; a page that
lays out its own box sets its height from the callback. With `autoHeight` (on by default when the host set no CSS height on
the canvas, forced either way by the option, never in compact mode) the renderer sets `canvas.style.height` itself and shows
every lane up to the cap. Every current host sets a height (/live 420 px, the hero 500 px, the app's card 220 px and its
Inspect view 420 px), so until a page opts in the frames are unchanged.
## The tests (all in `tools/ci/pre-push.sh`) ## The tests (all in `tools/ci/pre-push.sh`)
| Line | What it proves | | Line | What it proves |

View file

@ -1,5 +1,10 @@
/* Igneum DAG / Ember 2.0 (the EMBER 02 renderer pack, 7 Oct 2026). THE SHARED SOURCE: scene/live-dag.js; site/live-dag.js and /* Igneum DAG / Ember 2.0 (the EMBER 02 renderer pack, 7 Oct 2026). THE SHARED SOURCE: scene/live-dag.js; site/live-dag.js and
* app/igneum-app/ui/live-dag.js are byte-equal copies written by tools/scene/sync.mjs (the gate refuses a drifted copy). Edit here. * app/igneum-app/ui/live-dag.js are byte-equal copies written by tools/scene/sync.mjs (the gate refuses a drifted copy). Edit here.
* 2.0.4 (7 Oct 2026): the box's height follows the lanes. laneHeight (default 46 px, 40 on a phone, 26 compact) times the lanes
* shown (at most maxLanes, narrowLanes on a phone, never under 2) plus the axis and padding is the scene's wanted height;
* onSize(heightPx, {lanes, laneHeight, narrow, compact}) reports it whenever it changes, so a page can size the box, and with
* autoHeight (true by default when the host set no CSS height on the canvas, forced either way by the option; never in
* compact mode, the app's card keeps its own height) the renderer sets canvas.style.height itself and lets every lane through.
* 2.0.3 (scene parity, 7 Oct 2026): (1) every push, size and theme change paints the current picture at once; only the motion * 2.0.3 (scene parity, 7 Oct 2026): (1) every push, size and theme change paints the current picture at once; only the motion
* loop waits for a visible document and an intersecting canvas (a page that loads with document.hidden true, or whose embedder * loop waits for a visible document and an intersecting canvas (a page that loads with document.hidden true, or whose embedder
* never fires visibilitychange, showed a blank canvas while reporting live). (2) The narrow (phone) rule keys on the viewport * never fires visibilitychange, showed a blank canvas while reporting live). (2) The narrow (phone) rule keys on the viewport
@ -61,6 +66,10 @@
// narrowLanes lanes, nodes no smaller than narrowMinNode, hairlines off except the selected chain and the selected block, // narrowLanes lanes, nodes no smaller than narrowMinNode, hairlines off except the selected chain and the selected block,
// checkpoint labels as the percent alone. The owner's call of 7 Oct 2026: faster drift, more gap, on a phone. // checkpoint labels as the percent alone. The owner's call of 7 Oct 2026: faster drift, more gap, on a phone.
var narrow=false,narrowBreak=finite(opts.narrowBreak)?opts.narrowBreak:720,narrowWindow=clamp(finite(opts.narrowWindow)?opts.narrowWindow:30,30,300),narrowLanes=clamp(finite(opts.narrowLanes)?opts.narrowLanes:4,2,12),narrowMinNode=finite(opts.narrowMinNode)?opts.narrowMinNode:11,baseWindow=0,windowTouched=false; var narrow=false,narrowBreak=finite(opts.narrowBreak)?opts.narrowBreak:720,narrowWindow=clamp(finite(opts.narrowWindow)?opts.narrowWindow:30,30,300),narrowLanes=clamp(finite(opts.narrowLanes)?opts.narrowLanes:4,2,12),narrowMinNode=finite(opts.narrowMinNode)?opts.narrowMinNode:11,baseWindow=0,windowTouched=false;
// autoHeight: the host set no height on the canvas (its computed height is still the attribute's, 150 by default) and this
// is not the compact card; read before size() touches the attribute
var hostHeightSet=(function(){try{var cs=getComputedStyle(canvas),attr=canvas.getAttribute('height'),h=parseFloat(cs.height);return canvas.style.height!==''||!(finite(h)&&Math.round(h)===(attr===null?150:Number(attr)));}catch(e){return true;}})();
var autoHeight=opts.autoHeight===true||(opts.autoHeight!==false&&!compact&&!hostHeightSet),laneHeightOpt=finite(opts.laneHeight)?opts.laneHeight:0,wantH=0,laneHNow=46;
var maxLanes=clamp(finite(opts.maxLanes)?opts.maxLanes:7,2,12), minNode=finite(opts.minNode)?opts.minNode:9.5, hairAlpha=finite(opts.hairlineAlpha)?opts.hairlineAlpha:.12, laneReach=finite(opts.laneReach)?opts.laneReach:2, othersScale=finite(opts.othersScale)?opts.othersScale:.75, provenGlow=opts.provenGlow!==false; var maxLanes=clamp(finite(opts.maxLanes)?opts.maxLanes:7,2,12), minNode=finite(opts.minNode)?opts.minNode:9.5, hairAlpha=finite(opts.hairlineAlpha)?opts.hairlineAlpha:.12, laneReach=finite(opts.laneReach)?opts.laneReach:2, othersScale=finite(opts.othersScale)?opts.othersScale:.75, provenGlow=opts.provenGlow!==false;
var mq=root.matchMedia?root.matchMedia('(prefers-reduced-motion: reduce)'):null; var mq=root.matchMedia?root.matchMedia('(prefers-reduced-motion: reduce)'):null;
var reduced=mq?mq.matches:false, manualMotion=true; var reduced=mq?mq.matches:false, manualMotion=true;
@ -112,7 +121,8 @@
padL=compact||W<510?14:W<750?84:106;padR=compact?12:24;padT=compact?29:58;padB=compact?22:38; padL=compact||W<510?14:W<750?84:106;padR=compact?12:24;padT=compact?29:58;padB=compact?22:38;
var start=rightTime()-windowS*1000, end=rightTime()+lag+1000,counts=new Map(),mineKeys=new Set(); var start=rightTime()-windowS*1000, end=rightTime()+lag+1000,counts=new Map(),mineKeys=new Set();
blocks.forEach(function(b){if(b.ts>=start && b.ts<=end){counts.set(b.miner,(counts.get(b.miner)||0)+1);if(own(b))mineKeys.add(b.miner);}}); blocks.forEach(function(b){if(b.ts>=start && b.ts<=end){counts.set(b.miner,(counts.get(b.miner)||0)+1);if(own(b))mineKeys.add(b.miner);}});
var capacity=Math.min(narrow?narrowLanes:maxLanes,Math.max(2,Math.floor((H-padT-padB)/(compact?26:narrow?40:46)))),ids=Array.from(counts.keys()); var laneH=laneHeightOpt||(compact?26:narrow?40:46),cap=narrow?narrowLanes:maxLanes;laneHNow=laneH;
var capacity=autoHeight?cap:Math.min(cap,Math.max(2,Math.floor((H-padT-padB)/laneH))),ids=Array.from(counts.keys());
ids.sort(function(a,b){return (mineKeys.has(b)?1:0)-(mineKeys.has(a)?1:0)||(counts.get(b)-counts.get(a))||a.localeCompare(b);}); ids.sort(function(a,b){return (mineKeys.has(b)?1:0)-(mineKeys.has(a)?1:0)||(counts.get(b)-counts.get(a))||a.localeCompare(b);});
var chosen=ids.slice(0,ids.length>capacity?capacity-1:capacity); var chosen=ids.slice(0,ids.length>capacity?capacity-1:capacity);
// Preserve existing key order instead of re-ranking every poll. Always keep the user's lane visible. // Preserve existing key order instead of re-ranking every poll. Always keep the user's lane visible.
@ -121,6 +131,9 @@
keep.sort(function(a,b){return (mineKeys.has(b)?1:0)-(mineKeys.has(a)?1:0);}); keep.sort(function(a,b){return (mineKeys.has(b)?1:0)-(mineKeys.has(a)?1:0);});
lanes=keep;if(ids.length>chosen.length)lanes.push('__others__'); lanes=keep;if(ids.length>chosen.length)lanes.push('__others__');
laneOf=new Map();lanes.forEach(function(id,i){laneOf.set(id,i);}); laneOf=new Map();lanes.forEach(function(id,i){laneOf.set(id,i);});
// the wanted box height for these lanes (2.0.4): reported on change; applied here only under autoHeight
var nextH=padT+padB+Math.max(2,lanes.length)*laneH;
if(nextH!==wantH){wantH=nextH;emit('onSize',wantH,{lanes:lanes.length,laneHeight:laneH,narrow:narrow,compact:compact});if(autoHeight&&canvas.style.height!==wantH+'px')canvas.style.height=wantH+'px';}
var now=performance.now(); var now=performance.now();
blocks.forEach(function(b){var i=laneOf.has(b.miner)?laneOf.get(b.miner):Math.max(0,lanes.length-1); blocks.forEach(function(b){var i=laneOf.has(b.miner)?laneOf.get(b.miner):Math.max(0,lanes.length-1);
var y=padT+(i+.5)*(H-padT-padB)/Math.max(1,lanes.length); var y=padT+(i+.5)*(H-padT-padB)/Math.max(1,lanes.length);
@ -179,7 +192,9 @@
selectedChain:v.filter(function(b){return b.chain;}).length,proven:v.filter(function(b){return b.proven;}).length, selectedChain:v.filter(function(b){return b.chain;}).length,proven:v.filter(function(b){return b.proven;}).length,
own:v.filter(own).length,minerKeys:new Set(v.map(function(b){return b.miner;})).size, own:v.filter(own).length,minerKeys:new Set(v.map(function(b){return b.miner;})).size,
lockedCheckpoints:cps.filter(function(c){return c.state==='locked';}).length,paused:paused,following:following,queued:paused?queuedCount:0, lockedCheckpoints:cps.filter(function(c){return c.state==='locked';}).length,paused:paused,following:following,queued:paused?queuedCount:0,
invalidRecords:invalid,omittedRecords:omitted,rendered:blocks.length,frames:frameCount,window:windowS,narrow:narrow,lanes:lanes.length,reducedMotion:reduced||!manualMotion,state:state}; invalidRecords:invalid,omittedRecords:omitted,rendered:blocks.length,frames:frameCount,window:windowS,narrow:narrow,lanes:lanes.length,reducedMotion:reduced||!manualMotion,state:state,
// the lane geometry (2.0.4), so a page that sizes its own box reads no constants
laneHeight:laneHNow,padT:padT,padB:padB,capacity:narrow?narrowLanes:maxLanes,wantedHeight:wantH,autoHeight:autoHeight};
} }
function poll(){ function poll(){
if(destroyed||opts.poll===false||doc.hidden)return; if(destroyed||opts.poll===false||doc.hidden)return;
@ -356,11 +371,11 @@
function destroy(){if(destroyed)return;destroyed=true;stopFrame();clearTimeout(pollTimer);clearTimeout(timeout);clearInterval(healthTimer);if(controller)controller.abort();remove.forEach(function(fn){fn();});if(ro)ro.disconnect();if(io)io.disconnect();if(mo)mo.disconnect();if(tip)tip.hidden=true;canvas.classList.remove('engaged'); function destroy(){if(destroyed)return;destroyed=true;stopFrame();clearTimeout(pollTimer);clearTimeout(timeout);clearInterval(healthTimer);if(controller)controller.abort();remove.forEach(function(fn){fn();});if(ro)ro.disconnect();if(io)io.disconnect();if(mo)mo.disconnect();if(tip)tip.hidden=true;canvas.classList.remove('engaged');
[['tabindex',initialAttributes.tabindex],['role',initialAttributes.role],['aria-label',initialAttributes.label]].forEach(function(pair){if(pair[1]===null)canvas.removeAttribute(pair[0]);else canvas.setAttribute(pair[0],pair[1]);});canvas.style.touchAction=initialAttributes.touch;canvas.style.cursor=initialAttributes.cursor;instances.delete(canvas);model.clear();view.clear();blocks=[]; [['tabindex',initialAttributes.tabindex],['role',initialAttributes.role],['aria-label',initialAttributes.label]].forEach(function(pair){if(pair[1]===null)canvas.removeAttribute(pair[0]);else canvas.setAttribute(pair[0],pair[1]);});canvas.style.touchAction=initialAttributes.touch;canvas.style.cursor=initialAttributes.cursor;instances.delete(canvas);model.clear();view.clear();blocks=[];
} }
var api={push:push,setPaused:setPaused,zoom:zoom,engage:engage,getWindow:function(){return windowS;},getState:function(){return {state:state,reason:reason};},redraw:redraw,words:words, var api={push:push,setPaused:setPaused,zoom:zoom,engage:engage,getWindow:function(){return windowS;},getWantedHeight:function(){return wantH;},getState:function(){return {state:state,reason:reason};},redraw:redraw,words:words,
destroy:destroy,follow:follow,setWindow:setWindow,setFilter:setFilter,setMotion:setMotion,select:select,getStats:stats, destroy:destroy,follow:follow,setWindow:setWindow,setFilter:setFilter,setMotion:setMotion,select:select,getStats:stats,
getViewRange:function(){return {start:rightTime()-windowS*1000,end:rightTime(),filter:filter};},getBlocks:function(){return blocks.map(cloneBlock);},getCheckpoints:function(){return cps.map(function(c){return Object.assign({},c);});}, getViewRange:function(){return {start:rightTime()-windowS*1000,end:rightTime(),filter:filter};},getBlocks:function(){return blocks.map(cloneBlock);},getCheckpoints:function(){return cps.map(function(c){return Object.assign({},c);});},
getSnapshot:function(){return {ok:true,state:{stale:state!=='live'},blocks:blocks.map(cloneBlock),finality:{checkpoints:cps.map(function(c){return Object.assign({},c);})}};}}; getSnapshot:function(){return {ok:true,state:{stale:state!=='live'},blocks:blocks.map(cloneBlock),finality:{checkpoints:cps.map(function(c){return Object.assign({},c);})}};}};
instances.set(canvas,api);if(chip)chip.hidden=true;theme();size();emit('onState',state,reason);if(opts.poll!==false)poll();redraw();return api; instances.set(canvas,api);if(chip)chip.hidden=true;theme();size();emit('onState',state,reason);if(opts.poll!==false)poll();redraw();return api;
} }
root.IgneumDag={mount:mount,version:'2.0.3'}; root.IgneumDag={mount:mount,version:'2.0.4'};
})(window); })(window);

View file

@ -1,5 +1,10 @@
/* Igneum DAG / Ember 2.0 (the EMBER 02 renderer pack, 7 Oct 2026). THE SHARED SOURCE: scene/live-dag.js; site/live-dag.js and /* Igneum DAG / Ember 2.0 (the EMBER 02 renderer pack, 7 Oct 2026). THE SHARED SOURCE: scene/live-dag.js; site/live-dag.js and
* app/igneum-app/ui/live-dag.js are byte-equal copies written by tools/scene/sync.mjs (the gate refuses a drifted copy). Edit here. * app/igneum-app/ui/live-dag.js are byte-equal copies written by tools/scene/sync.mjs (the gate refuses a drifted copy). Edit here.
* 2.0.4 (7 Oct 2026): the box's height follows the lanes. laneHeight (default 46 px, 40 on a phone, 26 compact) times the lanes
* shown (at most maxLanes, narrowLanes on a phone, never under 2) plus the axis and padding is the scene's wanted height;
* onSize(heightPx, {lanes, laneHeight, narrow, compact}) reports it whenever it changes, so a page can size the box, and with
* autoHeight (true by default when the host set no CSS height on the canvas, forced either way by the option; never in
* compact mode, the app's card keeps its own height) the renderer sets canvas.style.height itself and lets every lane through.
* 2.0.3 (scene parity, 7 Oct 2026): (1) every push, size and theme change paints the current picture at once; only the motion * 2.0.3 (scene parity, 7 Oct 2026): (1) every push, size and theme change paints the current picture at once; only the motion
* loop waits for a visible document and an intersecting canvas (a page that loads with document.hidden true, or whose embedder * loop waits for a visible document and an intersecting canvas (a page that loads with document.hidden true, or whose embedder
* never fires visibilitychange, showed a blank canvas while reporting live). (2) The narrow (phone) rule keys on the viewport * never fires visibilitychange, showed a blank canvas while reporting live). (2) The narrow (phone) rule keys on the viewport
@ -61,6 +66,10 @@
// narrowLanes lanes, nodes no smaller than narrowMinNode, hairlines off except the selected chain and the selected block, // narrowLanes lanes, nodes no smaller than narrowMinNode, hairlines off except the selected chain and the selected block,
// checkpoint labels as the percent alone. The owner's call of 7 Oct 2026: faster drift, more gap, on a phone. // checkpoint labels as the percent alone. The owner's call of 7 Oct 2026: faster drift, more gap, on a phone.
var narrow=false,narrowBreak=finite(opts.narrowBreak)?opts.narrowBreak:720,narrowWindow=clamp(finite(opts.narrowWindow)?opts.narrowWindow:30,30,300),narrowLanes=clamp(finite(opts.narrowLanes)?opts.narrowLanes:4,2,12),narrowMinNode=finite(opts.narrowMinNode)?opts.narrowMinNode:11,baseWindow=0,windowTouched=false; var narrow=false,narrowBreak=finite(opts.narrowBreak)?opts.narrowBreak:720,narrowWindow=clamp(finite(opts.narrowWindow)?opts.narrowWindow:30,30,300),narrowLanes=clamp(finite(opts.narrowLanes)?opts.narrowLanes:4,2,12),narrowMinNode=finite(opts.narrowMinNode)?opts.narrowMinNode:11,baseWindow=0,windowTouched=false;
// autoHeight: the host set no height on the canvas (its computed height is still the attribute's, 150 by default) and this
// is not the compact card; read before size() touches the attribute
var hostHeightSet=(function(){try{var cs=getComputedStyle(canvas),attr=canvas.getAttribute('height'),h=parseFloat(cs.height);return canvas.style.height!==''||!(finite(h)&&Math.round(h)===(attr===null?150:Number(attr)));}catch(e){return true;}})();
var autoHeight=opts.autoHeight===true||(opts.autoHeight!==false&&!compact&&!hostHeightSet),laneHeightOpt=finite(opts.laneHeight)?opts.laneHeight:0,wantH=0,laneHNow=46;
var maxLanes=clamp(finite(opts.maxLanes)?opts.maxLanes:7,2,12), minNode=finite(opts.minNode)?opts.minNode:9.5, hairAlpha=finite(opts.hairlineAlpha)?opts.hairlineAlpha:.12, laneReach=finite(opts.laneReach)?opts.laneReach:2, othersScale=finite(opts.othersScale)?opts.othersScale:.75, provenGlow=opts.provenGlow!==false; var maxLanes=clamp(finite(opts.maxLanes)?opts.maxLanes:7,2,12), minNode=finite(opts.minNode)?opts.minNode:9.5, hairAlpha=finite(opts.hairlineAlpha)?opts.hairlineAlpha:.12, laneReach=finite(opts.laneReach)?opts.laneReach:2, othersScale=finite(opts.othersScale)?opts.othersScale:.75, provenGlow=opts.provenGlow!==false;
var mq=root.matchMedia?root.matchMedia('(prefers-reduced-motion: reduce)'):null; var mq=root.matchMedia?root.matchMedia('(prefers-reduced-motion: reduce)'):null;
var reduced=mq?mq.matches:false, manualMotion=true; var reduced=mq?mq.matches:false, manualMotion=true;
@ -112,7 +121,8 @@
padL=compact||W<510?14:W<750?84:106;padR=compact?12:24;padT=compact?29:58;padB=compact?22:38; padL=compact||W<510?14:W<750?84:106;padR=compact?12:24;padT=compact?29:58;padB=compact?22:38;
var start=rightTime()-windowS*1000, end=rightTime()+lag+1000,counts=new Map(),mineKeys=new Set(); var start=rightTime()-windowS*1000, end=rightTime()+lag+1000,counts=new Map(),mineKeys=new Set();
blocks.forEach(function(b){if(b.ts>=start && b.ts<=end){counts.set(b.miner,(counts.get(b.miner)||0)+1);if(own(b))mineKeys.add(b.miner);}}); blocks.forEach(function(b){if(b.ts>=start && b.ts<=end){counts.set(b.miner,(counts.get(b.miner)||0)+1);if(own(b))mineKeys.add(b.miner);}});
var capacity=Math.min(narrow?narrowLanes:maxLanes,Math.max(2,Math.floor((H-padT-padB)/(compact?26:narrow?40:46)))),ids=Array.from(counts.keys()); var laneH=laneHeightOpt||(compact?26:narrow?40:46),cap=narrow?narrowLanes:maxLanes;laneHNow=laneH;
var capacity=autoHeight?cap:Math.min(cap,Math.max(2,Math.floor((H-padT-padB)/laneH))),ids=Array.from(counts.keys());
ids.sort(function(a,b){return (mineKeys.has(b)?1:0)-(mineKeys.has(a)?1:0)||(counts.get(b)-counts.get(a))||a.localeCompare(b);}); ids.sort(function(a,b){return (mineKeys.has(b)?1:0)-(mineKeys.has(a)?1:0)||(counts.get(b)-counts.get(a))||a.localeCompare(b);});
var chosen=ids.slice(0,ids.length>capacity?capacity-1:capacity); var chosen=ids.slice(0,ids.length>capacity?capacity-1:capacity);
// Preserve existing key order instead of re-ranking every poll. Always keep the user's lane visible. // Preserve existing key order instead of re-ranking every poll. Always keep the user's lane visible.
@ -121,6 +131,9 @@
keep.sort(function(a,b){return (mineKeys.has(b)?1:0)-(mineKeys.has(a)?1:0);}); keep.sort(function(a,b){return (mineKeys.has(b)?1:0)-(mineKeys.has(a)?1:0);});
lanes=keep;if(ids.length>chosen.length)lanes.push('__others__'); lanes=keep;if(ids.length>chosen.length)lanes.push('__others__');
laneOf=new Map();lanes.forEach(function(id,i){laneOf.set(id,i);}); laneOf=new Map();lanes.forEach(function(id,i){laneOf.set(id,i);});
// the wanted box height for these lanes (2.0.4): reported on change; applied here only under autoHeight
var nextH=padT+padB+Math.max(2,lanes.length)*laneH;
if(nextH!==wantH){wantH=nextH;emit('onSize',wantH,{lanes:lanes.length,laneHeight:laneH,narrow:narrow,compact:compact});if(autoHeight&&canvas.style.height!==wantH+'px')canvas.style.height=wantH+'px';}
var now=performance.now(); var now=performance.now();
blocks.forEach(function(b){var i=laneOf.has(b.miner)?laneOf.get(b.miner):Math.max(0,lanes.length-1); blocks.forEach(function(b){var i=laneOf.has(b.miner)?laneOf.get(b.miner):Math.max(0,lanes.length-1);
var y=padT+(i+.5)*(H-padT-padB)/Math.max(1,lanes.length); var y=padT+(i+.5)*(H-padT-padB)/Math.max(1,lanes.length);
@ -179,7 +192,9 @@
selectedChain:v.filter(function(b){return b.chain;}).length,proven:v.filter(function(b){return b.proven;}).length, selectedChain:v.filter(function(b){return b.chain;}).length,proven:v.filter(function(b){return b.proven;}).length,
own:v.filter(own).length,minerKeys:new Set(v.map(function(b){return b.miner;})).size, own:v.filter(own).length,minerKeys:new Set(v.map(function(b){return b.miner;})).size,
lockedCheckpoints:cps.filter(function(c){return c.state==='locked';}).length,paused:paused,following:following,queued:paused?queuedCount:0, lockedCheckpoints:cps.filter(function(c){return c.state==='locked';}).length,paused:paused,following:following,queued:paused?queuedCount:0,
invalidRecords:invalid,omittedRecords:omitted,rendered:blocks.length,frames:frameCount,window:windowS,narrow:narrow,lanes:lanes.length,reducedMotion:reduced||!manualMotion,state:state}; invalidRecords:invalid,omittedRecords:omitted,rendered:blocks.length,frames:frameCount,window:windowS,narrow:narrow,lanes:lanes.length,reducedMotion:reduced||!manualMotion,state:state,
// the lane geometry (2.0.4), so a page that sizes its own box reads no constants
laneHeight:laneHNow,padT:padT,padB:padB,capacity:narrow?narrowLanes:maxLanes,wantedHeight:wantH,autoHeight:autoHeight};
} }
function poll(){ function poll(){
if(destroyed||opts.poll===false||doc.hidden)return; if(destroyed||opts.poll===false||doc.hidden)return;
@ -356,11 +371,11 @@
function destroy(){if(destroyed)return;destroyed=true;stopFrame();clearTimeout(pollTimer);clearTimeout(timeout);clearInterval(healthTimer);if(controller)controller.abort();remove.forEach(function(fn){fn();});if(ro)ro.disconnect();if(io)io.disconnect();if(mo)mo.disconnect();if(tip)tip.hidden=true;canvas.classList.remove('engaged'); function destroy(){if(destroyed)return;destroyed=true;stopFrame();clearTimeout(pollTimer);clearTimeout(timeout);clearInterval(healthTimer);if(controller)controller.abort();remove.forEach(function(fn){fn();});if(ro)ro.disconnect();if(io)io.disconnect();if(mo)mo.disconnect();if(tip)tip.hidden=true;canvas.classList.remove('engaged');
[['tabindex',initialAttributes.tabindex],['role',initialAttributes.role],['aria-label',initialAttributes.label]].forEach(function(pair){if(pair[1]===null)canvas.removeAttribute(pair[0]);else canvas.setAttribute(pair[0],pair[1]);});canvas.style.touchAction=initialAttributes.touch;canvas.style.cursor=initialAttributes.cursor;instances.delete(canvas);model.clear();view.clear();blocks=[]; [['tabindex',initialAttributes.tabindex],['role',initialAttributes.role],['aria-label',initialAttributes.label]].forEach(function(pair){if(pair[1]===null)canvas.removeAttribute(pair[0]);else canvas.setAttribute(pair[0],pair[1]);});canvas.style.touchAction=initialAttributes.touch;canvas.style.cursor=initialAttributes.cursor;instances.delete(canvas);model.clear();view.clear();blocks=[];
} }
var api={push:push,setPaused:setPaused,zoom:zoom,engage:engage,getWindow:function(){return windowS;},getState:function(){return {state:state,reason:reason};},redraw:redraw,words:words, var api={push:push,setPaused:setPaused,zoom:zoom,engage:engage,getWindow:function(){return windowS;},getWantedHeight:function(){return wantH;},getState:function(){return {state:state,reason:reason};},redraw:redraw,words:words,
destroy:destroy,follow:follow,setWindow:setWindow,setFilter:setFilter,setMotion:setMotion,select:select,getStats:stats, destroy:destroy,follow:follow,setWindow:setWindow,setFilter:setFilter,setMotion:setMotion,select:select,getStats:stats,
getViewRange:function(){return {start:rightTime()-windowS*1000,end:rightTime(),filter:filter};},getBlocks:function(){return blocks.map(cloneBlock);},getCheckpoints:function(){return cps.map(function(c){return Object.assign({},c);});}, getViewRange:function(){return {start:rightTime()-windowS*1000,end:rightTime(),filter:filter};},getBlocks:function(){return blocks.map(cloneBlock);},getCheckpoints:function(){return cps.map(function(c){return Object.assign({},c);});},
getSnapshot:function(){return {ok:true,state:{stale:state!=='live'},blocks:blocks.map(cloneBlock),finality:{checkpoints:cps.map(function(c){return Object.assign({},c);})}};}}; getSnapshot:function(){return {ok:true,state:{stale:state!=='live'},blocks:blocks.map(cloneBlock),finality:{checkpoints:cps.map(function(c){return Object.assign({},c);})}};}};
instances.set(canvas,api);if(chip)chip.hidden=true;theme();size();emit('onState',state,reason);if(opts.poll!==false)poll();redraw();return api; instances.set(canvas,api);if(chip)chip.hidden=true;theme();size();emit('onState',state,reason);if(opts.poll!==false)poll();redraw();return api;
} }
root.IgneumDag={mount:mount,version:'2.0.3'}; root.IgneumDag={mount:mount,version:'2.0.4'};
})(window); })(window);