From 945e6b2b6cd46c4f85ab1667e34d7653f3856d84 Mon Sep 17 00:00:00 2001 From: igneum-labs <337424239+igneum-labs@users.noreply.github.com> Date: Wed, 7 Oct 2026 13:58:33 +0000 Subject: [PATCH] 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 (cherry picked from commit 1546b61fa1959b7151f8a6f4e28c7c9763816acb) --- app/igneum-app/ui/live-dag.js | 23 +++++++++++++++++++---- scene/README.md | 10 +++++++++- scene/live-dag.js | 23 +++++++++++++++++++---- site/live-dag.js | 23 +++++++++++++++++++---- 4 files changed, 66 insertions(+), 13 deletions(-) diff --git a/app/igneum-app/ui/live-dag.js b/app/igneum-app/ui/live-dag.js index 0d28305d5..7911c0448 100644 --- a/app/igneum-app/ui/live-dag.js +++ b/app/igneum-app/ui/live-dag.js @@ -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 * 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 * 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 @@ -61,6 +66,10 @@ // 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. 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 mq=root.matchMedia?root.matchMedia('(prefers-reduced-motion: reduce)'):null; 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; 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);}}); - 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);}); 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. @@ -121,6 +131,9 @@ 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__'); 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(); 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); @@ -179,7 +192,9 @@ 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, 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(){ 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'); [['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, 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);})}};}}; 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); diff --git a/scene/README.md b/scene/README.md index e3bcca7ca..01b97463f 100644 --- a/scene/README.md +++ b/scene/README.md @@ -5,7 +5,7 @@ are ONE renderer fed ONE JSON shape. This folder is the source; everything else | 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` | | `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 | @@ -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 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`) | Line | What it proves | diff --git a/scene/live-dag.js b/scene/live-dag.js index 0d28305d5..7911c0448 100644 --- a/scene/live-dag.js +++ b/scene/live-dag.js @@ -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 * 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 * 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 @@ -61,6 +66,10 @@ // 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. 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 mq=root.matchMedia?root.matchMedia('(prefers-reduced-motion: reduce)'):null; 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; 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);}}); - 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);}); 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. @@ -121,6 +131,9 @@ 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__'); 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(); 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); @@ -179,7 +192,9 @@ 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, 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(){ 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'); [['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, 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);})}};}}; 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); diff --git a/site/live-dag.js b/site/live-dag.js index 0d28305d5..7911c0448 100644 --- a/site/live-dag.js +++ b/site/live-dag.js @@ -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 * 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 * 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 @@ -61,6 +66,10 @@ // 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. 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 mq=root.matchMedia?root.matchMedia('(prefers-reduced-motion: reduce)'):null; 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; 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);}}); - 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);}); 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. @@ -121,6 +131,9 @@ 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__'); 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(); 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); @@ -179,7 +192,9 @@ 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, 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(){ 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'); [['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, 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);})}};}}; 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);