diff --git a/app/igneum-app/ui/app.css b/app/igneum-app/ui/app.css index bde54a016..1474113f1 100644 --- a/app/igneum-app/ui/app.css +++ b/app/igneum-app/ui/app.css @@ -99,13 +99,15 @@ body.has-rail .top{left:var(--rail)} .brand{display:flex;align-items:center;gap:10px;min-width:0} .brand .word{font-family:var(--head);font-weight:900;font-size:20px;letter-spacing:.06em} .brand .miner{font-family:var(--mono);font-size:var(--t-xs);letter-spacing:.22em;color:var(--ash);margin-left:var(--s-1);padding-top:3px} +body.has-rail .top{align-items:baseline;padding-top:19px} .page-title{display:flex;align-items:baseline;gap:12px;min-width:0} .page-title h1{font-size:19px;font-weight:700;letter-spacing:0;white-space:nowrap} .page-sub{font-size:var(--t-base);color:var(--ash);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0} .top-right{display:flex;align-items:center;gap:var(--s-3);flex:0 0 auto;min-width:0} -.pill{display:inline-flex;align-items:center;gap:var(--s-2);font-family:var(--mono);font-size:var(--t-sm);letter-spacing:.08em;text-transform:uppercase;color:var(--ash);border:1px solid var(--line);border-radius:999px;padding:6px 12px 6px 10px;background:var(--graphite);white-space:nowrap;font-variant-numeric:tabular-nums;min-width:112px;justify-content:center} -.pill.on{color:var(--molten);border-color:var(--molten-40)} -.pill.warn{color:var(--ember)} +.pill{display:inline-flex;align-items:baseline;gap:var(--s-2);font-family:var(--sans);font-size:var(--t-base);font-weight:600;color:var(--ash);border:1px solid var(--line);border-radius:999px;padding:5px 12px 5px 10px;background:var(--graphite);white-space:nowrap;font-variant-numeric:tabular-nums;justify-content:center;line-height:1.3} +.pill .dot{position:relative;top:-1px} +.pill.on{color:var(--molten);border-color:var(--molten-40);background:var(--molten-10)} +.pill.warn{color:var(--ember);border-color:var(--ember-40);background:var(--ember-12)} .dot{width:8px;height:8px;border-radius:50%;background:var(--ash);display:inline-block;flex:0 0 8px} .on .dot,.dot.live{background:var(--molten);animation:pulse 2s ease-in-out infinite} .warn .dot,.dot.bad{background:var(--ember);animation:none} @@ -126,7 +128,11 @@ body.has-rail .notices{left:var(--rail)} .notice-close{flex:0 0 auto;width:30px;height:30px;border-radius:8px;border:1px solid transparent;background:transparent;color:var(--ash);font-size:20px;line-height:1;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;padding:0} .notice-close:hover{color:var(--bone);border-color:var(--line-2)} .notice.update-urgent .notice-close{color:#fff} -.notice-detail{flex-basis:100%;font-size:var(--t-sm);color:var(--ink-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:-3px} +.notice-more{flex:0 0 auto;width:30px;height:30px;border-radius:8px;border:1px solid transparent;background:transparent;color:var(--ash);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;padding:0} +.notice-more:hover{color:var(--bone);border-color:var(--line-2)} +.notice-more .chev{width:8px;height:8px;transform:rotate(45deg) translateY(-2px)} +.notice-more.open .chev{transform:rotate(225deg) translateY(-2px)} +.notice-detail{flex-basis:100%;font-family:var(--mono);font-size:var(--t-sm);color:var(--ink-2);white-space:normal;word-break:break-word;margin-top:-2px;user-select:text;-webkit-user-select:text} .notice .prog{flex-basis:100%;height:3px;background:rgba(127,127,127,.2);border-radius:2px;overflow:hidden;margin-top:-3px} .notice .prog i{display:block;height:100%;width:0;background:var(--ember);transition:width .5s linear} @@ -246,9 +252,10 @@ body[data-phase="welcome"] #screen-welcome,body[data-phase="cards"] #screen-card .feed>div:last-child{border-bottom:0} .feed>div>span:first-child{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} .feed .t{color:var(--ash);flex:0 0 auto;font-size:var(--t-xs)} -.feed .k{color:var(--molten);margin-right:6px} -.feed .k.error,.feed .k.warn,.feed .k.block{color:var(--ember)} -.feed .k.build,.feed .k.info{color:var(--ash)} +.feed .k{display:inline-block;width:7px;height:7px;border-radius:50%;background:var(--molten);margin-right:10px;position:relative;top:-1px} +.feed .k.error,.feed .k.warn,.feed .k.block{background:var(--ember)} +.feed .k.build,.feed .k.info{background:var(--line-2)} +.feed .k.proving{background:var(--nvidia)} /* the card rows on Mine (the hero object): badge, name and state, the numbers, Tune, the switch, the chevron; the tune line under; the details under that */ @@ -590,6 +597,8 @@ body.drawer-open .toast{bottom:calc(var(--drawer-h) + 16px)} .step{padding:32px 0} .money-row .v{font-size:var(--t-num)} .drawer-head{padding-left:var(--s-4);padding-right:var(--s-4)} + .notice{padding-left:var(--s-4);padding-right:var(--s-2);gap:var(--s-2)} + .notice-text{flex-basis:160px} .log,.log-ruler .t0,.log-ruler .t1{padding-left:var(--s-4);padding-right:var(--s-4)} } /* short windows (the 600 px floor): tighter paddings, a smaller canvas, so the drawer always has room */ @@ -605,3 +614,59 @@ body.drawer-open .toast{bottom:calc(var(--drawer-h) + 16px)} .drawer-head{padding-bottom:6px} .toggle-big{min-height:96px} } + +/* variant A, quieter: fewer borders, type carries the hierarchy */ +body[data-variant="a"] .card{border-color:transparent;padding:28px} +body[data-variant="a"] .card h3{font-size:20px} +body[data-variant="a"] .gpu-list{gap:0} +body[data-variant="a"] .gpu-line{border:0;border-radius:0;background:transparent;border-bottom:1px solid var(--line)} +body[data-variant="a"] .gpu-line:last-child{border-bottom:0} +body[data-variant="a"] .gl-main{padding:16px 0} +body[data-variant="a"] .gl-tune,body[data-variant="a"] .gl-details{border-top:0;padding-left:60px;padding-right:0;padding-top:0} +body[data-variant="a"] .gl-details{padding-bottom:16px} +body[data-variant="a"] .totals{border-color:transparent} +body[data-variant="a"] .toggle-big.stop{border-color:transparent} +body[data-variant="a"] .tot{border-left:0} +body[data-variant="a"] .num .v{font-size:22px} +body[data-variant="a"] .seg{border-color:transparent} +body[data-variant="a"] #dag{border:0} +/* variant B, denser: one line per card, numbers right-aligned in fixed tabular columns, a slim total bar */ +body[data-variant="b"] .hero-row{grid-template-columns:auto 1fr;gap:var(--s-3)} +body[data-variant="b"] .toggle-big{min-height:64px;flex-direction:row;align-items:center;gap:14px;padding:12px 18px} +body[data-variant="b"] .toggle-big .ring{margin:0} +body[data-variant="b"] .toggle-big .ts{display:none} +body[data-variant="b"] .totals{padding:10px 20px;align-items:center} +body[data-variant="b"] .tot .v{font-size:22px} +body[data-variant="b"] .tot .k{display:inline;margin:0 0 0 6px} +body[data-variant="b"] .tot .s{margin-top:0} +body[data-variant="b"] .card{padding:16px 20px} +body[data-variant="b"] .card-head{margin-bottom:8px} +body[data-variant="b"] .gpu-list{gap:0;border:1px solid var(--line);border-radius:var(--row-r);overflow:hidden} +body[data-variant="b"] .gpu-line{border:0;border-radius:0;border-top:1px solid var(--line)} +body[data-variant="b"] .gpu-line:first-child{border-top:0} +body[data-variant="b"] .gl-main{padding:8px 12px;gap:var(--s-3);grid-template-columns:32px minmax(140px,1fr) auto auto} +body[data-variant="b"] .badge{width:32px;height:32px;flex-basis:32px;border-radius:8px;font-size:9px} +body[data-variant="b"] .gpu-line .name{font-size:var(--t-base)} +body[data-variant="b"] .gpu-line .msg{display:none} +body[data-variant="b"] .nums{gap:0;flex-wrap:nowrap} +body[data-variant="b"] .num{min-width:92px;padding-left:8px} +body[data-variant="b"] .num .v{font-size:16px;font-family:var(--mono);font-weight:500} +body[data-variant="b"] .num .k{font-size:9px} +body[data-variant="b"] .gl-tune{padding:0 12px 6px 56px;border-top:0;font-size:var(--t-xs)} +body[data-variant="b"] .gl-tune .bar{height:2px} +body[data-variant="b"] .gl-details{padding-left:56px} +body[data-variant="b"] #dag{height:72px} +body[data-variant="b"] .feed>div{padding:4px 0} +/* variant C, the hero number: the fleet total big, the button a bar under it, the cards below */ +body[data-variant="c"] .hero-row{grid-template-columns:1fr} +body[data-variant="c"] .totals{order:1;grid-template-columns:2fr 1fr 1fr 1fr;align-items:end;padding:28px 28px 24px;border-color:transparent;background:linear-gradient(180deg,var(--graphite),var(--obsidian))} +body[data-variant="c"] .tot:first-child .v{font-size:84px;line-height:.95;letter-spacing:-.03em} +body[data-variant="c"] .tot:first-child .k{font-size:var(--t-sm);margin-top:10px} +body[data-variant="c"] .tot:first-child .s{font-size:var(--t-md)} +body[data-variant="c"] .tot .v{font-size:30px} +body[data-variant="c"] .tot{border-left:0} +body[data-variant="c"] .toggle-big{order:2;min-height:64px;flex-direction:row;align-items:center;gap:14px;padding:12px 20px} +body[data-variant="c"] .toggle-big .ring{margin:0} +body[data-variant="c"] .toggle-big .ts{margin-left:auto} +body[data-variant="c"] .gl-main{padding:10px 14px} +body[data-variant="c"] .num .v{font-size:18px} diff --git a/app/igneum-app/ui/app.js b/app/igneum-app/ui/app.js index 015497462..8b7319523 100644 --- a/app/igneum-app/ui/app.js +++ b/app/igneum-app/ui/app.js @@ -49,12 +49,27 @@ var Notices = (function () { } if (u.updated_from && s.uptime_s >= 0 && s.uptime_s < INSTALLED_S) { var now = s.version || ver; - return notice('update-installed', 'update:installed:' + now, 'Updated to Igneum Miner ' + now + ' from ' + u.updated_from + '.'); + var at = s.now > 0 && s.uptime_s >= 0 ? ' at ' + hhmm(s.now - s.uptime_s) : ''; + return notice('update-installed', 'update:installed:' + now, 'Updated to ' + now + at + '.', { title: 'Igneum Miner ' + now + ', from ' + u.updated_from }); } return null; } function jobTitle(j) { return j.title && j.title !== j.kind ? j.title : (j.kind === 'shard-benchmark' ? 'shard benchmark' : j.kind === 'build' ? 'build (mining continues)' : j.kind); } + // the job in a user's words (miner-ui-3 polish, 6 October 2026): from its kind, else from its id's prefix + function jobWord(kind, id) { + var k = String(kind || ''), i = String(id || ''); + var t = k || i; + if (/^update-now/.test(t) || /^update-now/.test(i)) return 'update check'; + if (/^restart/.test(t) || /^restart/.test(i)) return 'restart'; + if (/^collect/.test(t) || /^collect/.test(i)) return 'log collection'; + if (/shard-benchmark|^shard/.test(t) || /shard/.test(i)) return 'shard benchmark'; + if (/^build/.test(t) || /^build/.test(i)) return 'build'; + if (/ember|tune|sweep/.test(t) || /ember|tune|sweep/.test(i)) return 'tuning check'; + if (/script|playbook|run/.test(t)) return 'a script'; + return k || 'a task'; + } + function hhmm(t) { var d = new Date(t * 1000); return String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0'); } function firstErrorLine(results, summary) { for (var i = 0; i < results.length; i++) if (/BUILD FAILED|error|failed|panic/i.test(results[i])) return firstLine(results[i]); return firstLine(summary); @@ -65,18 +80,20 @@ var Notices = (function () { if (j.active) { var m = Math.max(0, Math.floor((now - j.started_at) / 60)); var results = (j.results || []).filter(function (x) { return x.indexOf('RESULT') === 0; }); - return notice('job-running', 'job:run:' + j.id, 'Job: ' + jobTitle(j) + ' running, ' + m + ' min.' + (j.stage ? ' ' + endDot(cap(j.stage)) : ''), { detail: results.length ? results[results.length - 1] : '', title: j.message || '' }); + var tech = [jobTitle(j), results.length ? results[results.length - 1] : ''].filter(Boolean).join(' · '); + return notice('job-running', 'job:run:' + j.id, 'A job from the team is running: ' + jobWord(j.kind, j.id) + ', ' + m + ' min.' + (j.stage ? ' ' + endDot(cap(j.stage)) : ''), { detail: tech, title: j.message || '' }); } var l = j.last; if (!l || !l.id) return null; var mins = Math.max(0, Math.round((l.finished_at - l.started_at) / 60)), t = l.title || l.kind; - var report = l.uploaded ? 'Report uploaded.' : 'Report not uploaded.'; + var report = l.uploaded ? '' : ' The report did not upload.', word = jobWord(l.kind, l.id), tech = t + (l.summary ? ': ' + l.summary : ''); if (l.status === 'done') { if (l.finished_at && now - l.finished_at > JOB_DONE_S) return null; - return notice('job-done', 'job:done:' + l.id, 'Job: ' + t + ' done after ' + mins + ' min. ' + report, { title: l.summary || '' }); + return notice('job-done', 'job:done:' + l.id, 'A job from the team ran: ' + word + ', ' + mins + ' min.' + report, { detail: tech, title: l.summary || '' }); } - var word = l.status === 'timeout' ? 'hit its time cap' : l.status === 'aborted' ? 'was stopped' : 'failed'; - return notice('job-failed', 'job:failed:' + l.id, 'Job: ' + t + ' ' + word + ' after ' + mins + ' min, exit ' + l.exit + '. ' + report, { tone: 'bad', detail: firstErrorLine(j.last_results || [], l.summary), title: l.summary || '' }); + var verb = l.status === 'timeout' ? 'ran out of time' : l.status === 'aborted' ? 'was stopped' : 'failed'; + var cause = firstErrorLine(j.last_results || [], l.summary); + return notice('job-failed', 'job:failed:' + l.id, 'A job from the team ' + verb + ': ' + word + ', after ' + mins + ' min.' + report, { tone: 'bad', detail: [tech, cause !== l.summary ? cause : ''].filter(Boolean).join(' · ') + ' · exit ' + l.exit, title: l.summary || '' }); } // c = state.clock: the engine's words, Sync clock, the manual hint underneath @@ -136,7 +153,7 @@ var Notices = (function () { } return best; } - return { tuneNotice: tuneNotice, LEVEL: LEVEL, JOB_DONE_S: JOB_DONE_S, INSTALLED_S: INSTALLED_S, CARD_S: CARD_S, updateNotice: updateNotice, jobNotice: jobNotice, clockNotice: clockNotice, cardNotices: cardNotices, gather: gather, pick: pick, jobTitle: jobTitle, cap: cap, endDot: endDot, kindWord: kindWord }; + return { tuneNotice: tuneNotice, jobWord: jobWord, hhmm: hhmm, LEVEL: LEVEL, JOB_DONE_S: JOB_DONE_S, INSTALLED_S: INSTALLED_S, CARD_S: CARD_S, updateNotice: updateNotice, jobNotice: jobNotice, clockNotice: clockNotice, cardNotices: cardNotices, gather: gather, pick: pick, jobTitle: jobTitle, cap: cap, endDot: endDot, kindWord: kindWord }; })(); /* ---------- the update card (pure; update-card.test.mjs loads this block) ---------- @@ -539,10 +556,111 @@ var View = (function () { if (text.length > max) { var cut = text.lastIndexOf(' ', max - 2); text = text.slice(0, cut > 40 ? cut : max - 2) + '…'; } return text; } + // the engine's event line in a user's voice (src/engine.rs event strings, 6 October 2026): {text, tech}; tech is + // the engine's own line when it differs, for the tooltip. Unknown lines keep their words with a JSON tail cut. + var EVENT_MAP = [ + [/^updated to Igneum Miner (\S+) from (\S+)/, function (m) { return 'Updated to ' + m[1]; }], + [/^Igneum Miner (\S+) is available: downloading/, function (m) { return 'Downloading ' + m[1]; }], + [/^Igneum Miner (\S+) is ready/, function (m) { return m[1] + ' is ready to install'; }], + [/^installing Igneum Miner (\S+)/, function (m) { return 'Installing ' + m[1]; }], + [/^update to (\S+) failed/, function (m) { return 'The update to ' + m[1] + ' failed'; }], + [/^the update to (\S+) did not take/, function (m) { return 'The update to ' + m[1] + ' did not take'; }], + [/^Igneum Miner (\S+) did not stay up twice; restoring (\S+)/, function (m) { return m[1] + ' did not stay up; going back to ' + m[2]; }], + [/^update downloaded/, function () { return 'Update downloaded'; }], + [/^OTA: administrator approval not given/, function () { return 'The update waits for the next time someone is at this PC'; }], + [/^job (\S+) \((.+)\) starts: /, function (m) { return 'A job from the team started: ' + Notices.jobWord(m[2], m[1]); }], + [/^job (\S+) \((.+)\) (done|failed|timeout|aborted) after (\d+) min/, function (m) { return 'A job from the team ' + (m[3] === 'done' ? 'ran' : m[3] === 'timeout' ? 'ran out of time' : m[3] === 'aborted' ? 'was stopped' : 'failed') + ': ' + Notices.jobWord('', m[1]) + ', ' + m[4] + ' min'; }], + [/^(\d+) remote jobs? received from Igneum/, function (m) { return m[1] + ' job' + (m[1] === '1' ? '' : 's') + ' from the team received'; }], + [/^remote job: update check now/, function () { return 'The team asked for an update check'; }], + [/^remote job: the app restarts/, function () { return 'The team asked the app to restart'; }], + [/^remote job asked for a node restart, but the node is external/, function () { return 'The team asked for a node restart; this node is not the app’s, so nothing was done'; }], + [/^remote jobs allowed/, function () { return 'Jobs from the team can run here'; }], + [/^remote jobs switched off/, function () { return 'Jobs from the team are off'; }], + [/^job finished; the miners restart/, function () { return 'The job finished; mining restarts'; }], + [/^node synced: (\d+) blocks, (\d+) peer/, function (m) { return 'Node synced: ' + withCommas(+m[1]) + ' blocks, ' + m[2] + ' peer' + (m[2] === '1' ? '' : 's'); }], + [/^a node already answers on/, function () { return 'Using the node already running on this machine'; }], + [/^consensus parameters from the signed manifest/, function () { return 'Rule settings read from the team'; }], + [/^this node build does not understand the consensus parameters/, function () { return 'This node is too old for the team’s rule settings; an app update carries the newer node'; }], + [/^restarting the node with the new consensus parameters/, function () { return 'Restarting the node with the new rule settings'; }], + [/^kernel tuning from the signed manifest/, function () { return 'Tuning starting points read from the team'; }], + [/^block accepted by the node \((.+)\)/, function (m) { return 'Block found by ' + m[1]; }], + [/^igneumd exited with code (\d+) after (\d+) s; restarting/, function (m) { return 'The node stopped (code ' + m[1] + '); restarting'; }], + [/^igneumd exited at once \(code (\d+)\)/, function (m) { return 'The node stopped at once (code ' + m[1] + ')'; }], + [/^igneumd could not start/, function () { return 'The node could not start'; }], + [/^miner (\S+) exited with code (\d+); restarting in (\d+) s/, function (m) { return 'A worker stopped (code ' + m[2] + '); restarting in ' + m[3] + ' s'; }], + [/^no reading from the node for (\d+) s/, function (m) { return 'No reading from the node for ' + m[1] + ' s; restarting it'; }], + [/^no peer after 90 s/, function () { return 'No other node found after 90 s; still looking'; }], + [/^no payout address/, function () { return 'No rewards address yet: finish the setup'; }], + [/^mining paused; the node keeps running/, function () { return 'Mining paused; the node keeps running'; }], + [/^mining resumed/, function () { return 'Mining resumed'; }], + [/^GPU power cap in force: (.+)/, function (m) { return 'Power cap in force: ' + m[1]; }], + [/^GPU power cap NOT applied/, function () { return 'Power cap not applied: the administrator prompt was refused (Retry on the card)'; }], + [/^power control on:/, function () { return 'Power control on'; }], + [/^rewards go to (\S+)/, function (m) { return 'Rewards go to ' + shortHex(m[1], 6, 4); }], + [/^settings changed \((.+)\); the miners restart/, function (m) { return 'Settings changed (' + m[1] + '); mining restarts'; }], + [/^(.+): tune queued; it starts once/, function (m) { return m[1] + ': tune queued'; }], + [/^(.+): tuning started: /, function (m) { return m[1] + ': tuning started'; }], + [/^(.+): best point (.+?) \((.+?)\); your setting stays pinned/, function (m) { return m[1] + ': best point ' + m[2] + ' (' + m[3] + '); your cap stays pinned'; }], + [/^(.+): measured as it runs: (.+)/, function (m) { return m[1] + ': measured ' + m[2]; }], + [/^(.+): no tune: (.+)/, function (m) { return m[1] + ': no tune (' + m[2] + ')'; }], + [/^(.+): worker fault: (.+); the miner restarts the worker/, function (m) { return m[1] + ': worker fault; restarting it'; }], + [/^(.+): the worker could not compile the next program/, function (m) { return m[1] + ': the next program compiles at the hour boundary instead'; }], + [/^(.+) worker ready/, function (m) { return m[1] + ': worker ready'; }], + [/^(.+) kernel race: (.+?) at ([\d.]+) MH\/s/, function (m) { return m[1] + ': program variant chosen, ' + m[3] + ' MH/s'; }], + [/^(.+): switched on, (\d+) identit/, function (m) { return m[1] + ': on'; }], + [/^(.+): switched off/, function (m) { return m[1] + ': off'; }], + [/^(.+): (\d+) identit(y|ies); its worker restarts/, function (m) { return m[1] + ': ' + m[2] + ' identit' + m[3] + '; its worker restarts'; }], + [/^next hourly program compiled and ready/, function () { return 'Next hourly program ready'; }], + [/^the hourly program changed; the worker restarts/, function () { return 'New hourly program; the worker restarts on it'; }], + [/^hourly program changed: /, function () { return 'New hourly program'; }], + [/^program pack out of date, rebuilding/, function () { return 'Rebuilding the program pack'; }], + [/^GPU worker built from source/, function () { return 'Worker built'; }], + [/^block (\d+) shard (\d+) paid (.+) IGN/, function (m) { return 'Proof paid: ' + m[3] + ' IGN'; }], + [/^block (\d+) shard (\d+) proven and submitted in (\d+) s/, function (m) { return 'Proof sent in ' + m[3] + ' s'; }], + [/^segment .+ paid (.+) IGN to the aggregator/, function (m) { return 'Segment paid: ' + m[1] + ' IGN'; }], + [/^segment (\S+)\.\.(\S+) aggregated and submitted in (\d+) s/, function (m) { return 'Segment proven in ' + m[3] + ' s'; }], + [/^segment (\S+)\.\.(\S+): (\d+) shards proven, aggregated and submitted in (\d+) s/, function (m) { return 'Segment proven: ' + m[3] + ' pieces in ' + m[4] + ' s'; }], + [/^segment (\S+)\.\.(\S+): (\d+) shards proven and submitted in (\d+) s; the segment record is held/, function (m) { return 'Segment proven: ' + m[3] + ' pieces in ' + m[4] + ' s; its record waits for the chain rule'; }], + [/^segment (\S+)\.\.(\S+) record accepted on retry/, function () { return 'Segment record accepted'; }], + [/^WSL2 prover setup started/, function () { return 'Prover setup started in its own window'; }], + [/^Card removed: (.+); its worker stopped/, function (m) { return 'Card removed: ' + m[1]; }], + [/^(.+): not usable \((.+?)\); its worker stopped/, function (m) { return m[1] + ': not usable (' + m[2] + ')'; }], + [/^clock agrees with the network again/, function () { return 'Clock agrees with the network again'; }], + [/^asking the system to set its clock/, function () { return 'Setting the clock from a time server'; }], + [/^clock: (.+)/, function (m) { return 'Clock: ' + m[1]; }], + [/^devnet only: the node trusts proof records/, function () { return 'Devnet trust mode: proof records are not verified (Settings)'; }], + [/^a sweep is setting the caps right now/, function () { return 'A tune is setting the caps now; retry when it is done'; }], + [/^updates are off for this engine/, function () { return 'Updates are off for this engine (a measurement run)'; }], + [/^(.+); the node restarts$/, function (m) { return cap(m[1]) + '; the node restarts'; }], + [/^(.+); the node is external, so the app cannot restart it/, function (m) { return cap(m[1]) + '; the node is not the app’s, so it is not restarted'; }] + ]; + function plainEvent(text) { + text = String(text || ''); + for (var i = 0; i < EVENT_MAP.length; i++) { var m = EVENT_MAP[i][0].exec(text); if (m) { var out = EVENT_MAP[i][1](m); return { text: out, tech: out === text ? '' : text }; } } + var cut = eventLine(text); + return { text: cap(cut), tech: cut === text ? '' : text }; + } + // the pill in the top bar: a verb a user understands, and its tone (on = molten, bad = ember, '' = grey) + function pill(s) { + if (s.quitting) return { text: 'Stopping', tone: '' }; + if (!s.setup_done) return { text: s.detecting ? 'Detecting cards' : 'Setting up', tone: '' }; + var m = s.mining || {}, n = s.node || {}; + if (tuningCard(m.cards)) return { text: 'Tuning', tone: 'on' }; + if (m.state === 'mining') return { text: 'Mining · ' + (m.hash_total >= 100 ? m.hash_total.toFixed(0) : (m.hash_total || 0).toFixed(1)) + ' MH/s', tone: 'on' }; + if (m.state === 'paused') return { text: 'Paused', tone: '' }; + if (n.state === 'syncing') return { text: 'Syncing the node', tone: '' }; + if (n.state === 'starting') return { text: 'Starting the node', tone: '' }; + if (n.state === 'restarting') return { text: 'Node restarting', tone: 'bad' }; + if (n.state === 'failed') return { text: 'Node failed', tone: 'bad' }; + if (n.state === 'stopped') return { text: 'Node stopped', tone: 'bad' }; + if (m.state === 'waiting') return { text: 'Starting the worker', tone: '' }; + if (m.state === 'idle') return { text: 'Not mining', tone: '' }; + return { text: cap(m.state || 'starting'), tone: '' }; + } // the confirmations' words (in place, never a dialog) function addressAsk(a) { return 'Pay ' + shortHex(a, 6, 4) + ' from the next block? The miner restarts.'; } function trustAsk(on) { return on ? 'Include proof records the node never checked? Devnet only; the node restarts.' : 'Verify proof records again? The node restarts.'; } - return { PAGES: PAGES, page: page, withCommas: withCommas, compact: compact, rel: rel, ago: ago, shortHex: shortHex, kindWord: kindWord, shownCards: shownCards, present: present, cardTitle: cardTitle, cardRow: cardRow, rowWord: rowWord, tuneWord: tuneWord, tuneEta: tuneEta, tuningCard: tuningCard, poundsPerDay: poundsPerDay, money: money, effOf: effOf, effText: effText, wattsTotal: wattsTotal, noReading: noReading, cells: cells, nextCheck: nextCheck, tunePoint: tunePoint, tunable: tunable, tuneWords: tuneWords, schedule: schedule, GOALS: GOALS, goalWords: goalWords, toggle: toggle, nodeWords: nodeWords, nodeLine: nodeLine, skewWord: skewWord, peersLine: peersLine, heightLine: heightLine, nextSwitch: nextSwitch, switchLine: switchLine, proveWords: proveWords, verifierWords: verifierWords, proveTier: proveTier, proveLine: proveLine, devFeeText: devFeeText, devFeeLine: devFeeLine, jobsNote: jobsNote, eventLine: eventLine, addressAsk: addressAsk, trustAsk: trustAsk }; + return { PAGES: PAGES, page: page, withCommas: withCommas, compact: compact, rel: rel, ago: ago, shortHex: shortHex, kindWord: kindWord, shownCards: shownCards, present: present, cardTitle: cardTitle, cardRow: cardRow, rowWord: rowWord, tuneWord: tuneWord, tuneEta: tuneEta, tuningCard: tuningCard, poundsPerDay: poundsPerDay, money: money, effOf: effOf, effText: effText, wattsTotal: wattsTotal, noReading: noReading, cells: cells, nextCheck: nextCheck, tunePoint: tunePoint, tunable: tunable, tuneWords: tuneWords, schedule: schedule, GOALS: GOALS, goalWords: goalWords, toggle: toggle, nodeWords: nodeWords, nodeLine: nodeLine, skewWord: skewWord, peersLine: peersLine, heightLine: heightLine, nextSwitch: nextSwitch, switchLine: switchLine, proveWords: proveWords, verifierWords: verifierWords, proveTier: proveTier, proveLine: proveLine, devFeeText: devFeeText, devFeeLine: devFeeLine, jobsNote: jobsNote, eventLine: eventLine, plainEvent: plainEvent, pill: pill, addressAsk: addressAsk, trustAsk: trustAsk }; })(); /* ---------- Ember Tune: the card row's tuning line (pure; tune-line.test.mjs loads this block) ---------- One line per card from the card state (src/state.rs, src/ember.rs): running (the phase and the step), tuned @@ -589,6 +707,7 @@ if (typeof document !== 'undefined') (function () { var forcedUpdate = params.get('update'); // a sample update state for screenshots: available|downloading|ready|waiting|applying|urgent|manual|error|updated var forcedJob = params.get('job'); // a sample remote job for screenshots: running|done|failed if (params.get('host') === 'mac') document.body.classList.add('mac'); + if (params.get('variant')) document.body.dataset.variant = params.get('variant'); // ?variant=a|b|c: the Mine layouts of the 6 October polish round // ?debug=1 prints a budget line every 5 s (ms of JS per second in the canvas, the state render and the log) and // exposes the counters as window.__igneumPerf var debug = params.get('debug') === '1'; @@ -1311,7 +1430,7 @@ if (typeof document !== 'undefined') (function () { var key = s.events.length ? s.events[0].t + ':' + s.events.length : ''; if (key !== lastEventsKey) { lastEventsKey = key; - $('d-events').innerHTML = s.events.length ? s.events.slice(0, 8).map(function (e) { return '