diff --git a/app/igneum-app/ui/app.css b/app/igneum-app/ui/app.css index bde54a01..623e74fa 100644 --- a/app/igneum-app/ui/app.css +++ b/app/igneum-app/ui/app.css @@ -61,7 +61,7 @@ input,textarea{font-variant-numeric:tabular-nums} .btn.tiny{min-height:26px;padding:2px 10px;font-size:var(--t-sm);border-radius:7px;font-family:var(--mono);font-weight:500} .btn.ghost{border-color:transparent;color:var(--ink-2)} .btn.ghost:hover{border-color:var(--line-2);color:var(--bone)} -.btn.ghost.on{color:var(--molten);border-color:var(--molten-40);background:var(--molten-10)} +.btn.ghost.on{color:var(--molten);border-color:var(--molten-40)} .btn.danger:hover{color:var(--ember);border-color:var(--ember)} :focus-visible{outline:2px solid var(--ember);outline-offset:3px;border-radius:6px} @media (prefers-reduced-motion:reduce){.btn{transition:none}} @@ -77,7 +77,7 @@ body.mac .rail-brand{padding-top:30px} .nav svg{width:19px;height:19px;flex:0 0 19px;fill:none;stroke:currentColor;stroke-width:1.9;stroke-linecap:round;stroke-linejoin:round;color:var(--ash);transition:color .15s ease} .nav:hover{background:var(--hover);color:var(--bone)} .nav:hover svg{color:var(--ink-2)} -.nav.on{background:var(--ember-12);border-color:var(--ember-40);color:var(--bone);font-weight:600} +.nav.on{background:var(--hover);border-color:var(--line);color:var(--bone);font-weight:600} .nav.on svg{color:var(--ember)} .nav.on::before{content:"";position:absolute;left:-13px;top:10px;bottom:10px;width:3px;border-radius:0 3px 3px 0;background:var(--ember)} .nav.small{min-height:36px;font-size:var(--t-base);color:var(--ash)} @@ -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{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)} -.pill.warn{color:var(--ember)} +.pill.warn{color:var(--ember);border-color:var(--ember-40)} .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} @@ -116,26 +118,30 @@ body.has-rail .top{left:var(--rail)} top moves once per change with a 150 ms transition (layoutStrip), never per poll. */ .notices{position:fixed;top:var(--top);left:0;right:0;z-index:19} body.has-rail .notices{left:var(--rail)} -.notice{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s-2) var(--s-4);padding:8px calc(var(--gutter) - 6px) 8px var(--gutter);background:var(--molten-10);border-bottom:1px solid var(--molten-40);font-size:var(--t-base);line-height:1.4;color:var(--bone)} -.notice.bad{background:var(--ember-12);border-bottom-color:var(--ember-40)} -.notice.warn{background:var(--molten-10);border-bottom-color:var(--molten-40)} -.notice.update-urgent{background:rgba(242,84,27,.55);border-bottom-color:var(--ember);color:#fff;font-weight:600} +.notice{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s-2) var(--s-3);padding:9px calc(var(--gutter) - 6px) 9px var(--gutter);background:var(--row);border-bottom:1px solid var(--line);box-shadow:inset 0 1px 0 var(--ember);font-size:var(--t-base);line-height:1.4;color:var(--ash)} +.notice-dot{width:7px;height:7px;border-radius:50%;background:var(--ember);flex:0 0 7px;display:inline-block} +.notice.bad .notice-text,.notice.update-urgent .notice-text{color:var(--bone)} +.notice.update-urgent .notice-text{font-weight:600} +.notice .notice-text b{color:var(--bone);font-weight:600} .notice-text{flex:1 1 320px;min-width:0} .notice-actions{display:flex;align-items:center;gap:var(--s-2);flex:0 0 auto} .notice-actions:empty{display:none} .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 .prog{flex-basis:100%;height:3px;background:rgba(127,127,127,.2);border-radius:2px;overflow:hidden;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:2px;background:var(--line);border-radius:2px;overflow:hidden;margin-top:-3px} .notice .prog i{display:block;height:100%;width:0;background:var(--ember);transition:width .5s linear} /* a question in place of a dialog: the quit strip over the page, the inline asks inside a card */ .ask-wrap{position:fixed;left:0;right:0;bottom:0;z-index:36;display:flex;justify-content:center;padding:0 var(--s-4) var(--s-4);pointer-events:none} body.has-rail .ask-wrap{left:var(--rail)} -.ask{display:flex;align-items:center;gap:var(--s-3);flex-wrap:wrap;background:var(--graphite);border:1px solid var(--ember-40);border-radius:14px;padding:12px 16px;box-shadow:0 14px 40px var(--shadow);pointer-events:auto;max-width:720px;animation:rise .2s ease} +.ask{display:flex;align-items:center;gap:var(--s-3);flex-wrap:wrap;background:var(--row);border:1px solid var(--line);border-radius:14px;padding:12px 16px;box-shadow:inset 0 1px 0 var(--ember),0 14px 40px var(--shadow);pointer-events:auto;max-width:720px;animation:rise .2s ease} .ask-text{flex:1 1 260px;font-size:var(--t-md);min-width:0} -.ask.inline{box-shadow:none;background:var(--ember-12);margin-top:var(--s-3);animation:none} +.ask.inline{box-shadow:inset 0 1px 0 var(--ember);background:var(--row);margin-top:var(--s-3);animation:none} /* screens and pages */ main{position:absolute;top:var(--top);bottom:var(--bottom);left:0;right:0;overflow:auto;padding:0 var(--gutter);overscroll-behavior:contain;transition:top .15s ease} @@ -214,7 +220,7 @@ body[data-phase="welcome"] #screen-welcome,body[data-phase="cards"] #screen-card .toggle-big .ts{font-family:var(--mono);font-size:var(--t-xs);letter-spacing:.06em;opacity:.8;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%} .toggle-big.stop{background:var(--graphite);border-color:var(--line-2);color:var(--bone)} .toggle-big.stop:hover{border-color:var(--ash);background:var(--row)} -.toggle-big.stop .ring{background:var(--ember-12);color:var(--ember)} +.toggle-big.stop .ring{background:var(--obsidian);border:1px solid var(--line-2);color:var(--ember)} .toggle-big.stop .ts{color:var(--molten);opacity:1} .totals{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:var(--s-3);background:var(--graphite);border:1px solid var(--line);border-radius:14px;padding:18px 20px;min-width:0} .tot{display:flex;flex-direction:column;min-width:0;padding-left:var(--s-4);border-left:1px solid var(--line)} @@ -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 */ @@ -349,8 +356,8 @@ table{border-collapse:collapse;width:100%;font-size:var(--t-base)} #s-jobs-history td{padding:6px 6px 6px 0;border-top:1px solid var(--line);vertical-align:top} #s-jobs-history tr.failed td,#s-jobs-history tr.timeout td,#s-jobs-history tr.aborted td{color:var(--ember)} #s-jobs-history tr.running td{color:var(--molten)} -.clock-card{border:1px solid var(--ember-40);background:var(--ember-12);border-radius:12px;padding:12px 14px;display:flex;flex-direction:column;gap:var(--s-2)} -.clock-card.warn{border-color:var(--molten-40);background:var(--molten-10)} +.clock-card{border:1px solid var(--line);background:var(--row);box-shadow:inset 0 1px 0 var(--ember);border-radius:12px;padding:12px 14px;display:flex;flex-direction:column;gap:var(--s-2)} +.clock-card.warn{box-shadow:inset 0 1px 0 var(--molten)} .clock-msg{font-size:var(--t-md);color:var(--bone);line-height:1.45} .clock-card .note{margin-top:0} @@ -372,7 +379,7 @@ table{border-collapse:collapse;width:100%;font-size:var(--t-base)} .options{display:flex;flex-direction:column;gap:var(--s-3);margin-top:6px} .option{display:flex;gap:var(--s-4);align-items:flex-start;background:var(--graphite);border:1px solid var(--line);border-radius:var(--card-r);padding:20px 22px;cursor:pointer;transition:border-color .15s ease,background .15s ease} .option:hover{border-color:var(--line-2)} -.option.on{border-color:var(--ember);background:var(--ember-12)} +.option.on{border-color:var(--ember);background:var(--row)} .option input[type=radio]{position:absolute;opacity:0;width:0;height:0} .option .radio{width:20px;height:20px;border-radius:50%;border:2px solid var(--line-2);flex:0 0 20px;margin-top:2px;position:relative} .option.on .radio{border-color:var(--ember)} @@ -506,8 +513,8 @@ body.drawer-drag main{pointer-events:none} .log .src.app{color:var(--ember)} .log .src.watch{color:var(--ash)} .log .e,.log .e .src{color:var(--ember)} -.log .ln.hit{background:rgba(255,179,92,.18);box-shadow:inset 3px 0 0 var(--molten);margin:0 calc(-1 * var(--gutter));padding:0 var(--gutter)} -.log mark{background:rgba(255,179,92,.3);color:var(--bone);border-radius:2px;padding:0 1px} +.log .ln.hit{background:var(--hover);box-shadow:inset 3px 0 0 var(--molten);margin:0 calc(-1 * var(--gutter));padding:0 var(--gutter)} +.log mark{background:transparent;color:var(--bone);text-decoration:underline;text-decoration-color:var(--ember);text-underline-offset:2px} .log-empty{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:var(--ash);font-size:var(--t-sm);padding:0 var(--gutter);text-align:center} .log-jump{position:absolute;right:calc(var(--gutter) + 14px);bottom:14px;background:var(--graphite);border-color:var(--molten-40);color:var(--molten);box-shadow:0 8px 24px var(--shadow);z-index:2;gap:6px;padding:6px 12px} .log-jump:hover{border-color:var(--molten)} @@ -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 */ diff --git a/app/igneum-app/ui/app.js b/app/igneum-app/ui/app.js index 01549746..92f93165 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 @@ -1311,7 +1429,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 '
' + esc(e.kind) + '' + esc(View.eventLine(e.text)) + '' + clock(e.t) + '
'; }).join('') : '
No events yet. The node and the miner report here as they start.
'; + $('d-events').innerHTML = s.events.length ? s.events.slice(0, 8).map(function (e) { var pe = View.plainEvent(e.text); return '' + esc(pe.text) + '' + clock(e.t) + ''; }).join('') : '
No events yet. The node and the miner report here as they start.
'; } } // the Tuning card on Mine and the Tuning rows on Settings share the goal, the schedule and the Power control switch @@ -1442,18 +1560,10 @@ if (typeof document !== 'undefined') (function () { else if (page === 'settings') renderSettings(s); } function renderPill(s) { - var pill = $('pill'), text; + var pill = $('pill'), pw = View.pill(s); pill.classList.remove('on', 'warn'); - if (s.quitting) text = 'stopping'; - else if (!s.setup_done) text = s.detecting ? 'detecting' : 'set up'; - else if (View.tuningCard(s.mining.cards)) { text = 'tuning'; pill.classList.add('on'); } - else if (s.mining.state === 'mining') { text = s.mining.hash_total.toFixed(1) + ' MH/s'; pill.classList.add('on'); } - else if (s.mining.state === 'paused') text = 'paused'; - else if (s.node.state === 'syncing' || s.node.state === 'starting') text = 'node ' + s.node.state; - else if (s.node.state === 'failed' || s.node.state === 'restarting') { text = 'node ' + s.node.state; pill.classList.add('warn'); } - else if (s.mining.state === 'idle' && s.node.state === 'synced') { text = 'node synced'; pill.classList.add('on'); } - else text = s.mining.state; - $('pill-text').textContent = text; + if (pw.tone === 'on') pill.classList.add('on'); else if (pw.tone === 'bad') pill.classList.add('warn'); + $('pill-text').textContent = pw.text; var foot = (s.display_name ? '' + esc(s.display_name) + '
' : '') + (s.address.display ? 'pays ' + esc(s.address.display.slice(0, 6) + '…' + s.address.display.slice(-4)) : 'no address yet') + '
up ' + uptime(s.uptime_s); if (foot !== lastFoot) { lastFoot = foot; $('rail-status').innerHTML = foot; } var fv = 'v' + esc(s.version) + ' · ' + esc(s.chain || s.network || '') + ''; @@ -1492,7 +1602,8 @@ if (typeof document !== 'undefined') (function () { el.className = 'notice ' + n.kind + (n.tone ? ' ' + n.tone : ''); el.dataset.key = n.key; $('notice-text').textContent = n.text; $('notice-text').title = n.title || ''; - $('notice-detail').textContent = n.detail; $('notice-detail').hidden = !n.detail; $('notice-detail').title = n.detail; + $('notice-detail').textContent = n.detail; $('notice-detail').hidden = true; $('notice-detail').title = n.detail; + $('notice-more').hidden = !n.detail; $('notice-more').setAttribute('aria-expanded', 'false'); $('notice-more').classList.remove('open'); $('notice-actions').innerHTML = n.actions.map(function (a) { return ''; }).join(''); $('notice-prog').hidden = !(n.progress >= 0); strip.hidden = false; @@ -1514,6 +1625,7 @@ if (typeof document !== 'undefined') (function () { var b = e.target.closest('[data-act]'); if (!b || b.disabled) return; var act = b.dataset.act, key = $('notice').dataset.key; if (act === 'close') { dismissed[key] = true; renderNotices(state || {}); } + else if (act === 'more') { var open = $('notice-detail').hidden; $('notice-detail').hidden = !open; b.setAttribute('aria-expanded', open ? 'true' : 'false'); b.classList.toggle('open', open); layoutStrip(); } else if (act === 'install') { api('api/update/install', {}); toast('Installing at once'); } else if (act === 'retry') { api('api/update/install', {}); toast('Trying the update again'); } else if (act === 'open') api('api/update/open', {}); @@ -1659,7 +1771,7 @@ if (typeof document !== 'undefined') (function () { }).catch(function () { failures++; if (failures >= 4) { - $('pill').classList.add('warn', 'away'); $('pill-text').textContent = state && state.quitting ? 'stopped' : 'engine away'; + $('pill').classList.add('warn', 'away'); $('pill-text').textContent = state && state.quitting ? 'Stopped' : 'Engine not answering'; if (phase === 'dashboard') { setText('node-line-text', 'Node: no answer'); setText('node-sub', 'the engine is not answering; the window reconnects by itself'); $('node-card').classList.add('bad'); setText('btn-toggle-sub', 'the engine is not answering'); $('btn-toggle').disabled = true; diff --git a/app/igneum-app/ui/index.html b/app/igneum-app/ui/index.html index da1cfcee..46655052 100644 --- a/app/igneum-app/ui/index.html +++ b/app/igneum-app/ui/index.html @@ -48,8 +48,10 @@