miner-ui-3: the card row is the product (audit, design, build)
The audit (docs/plans/miner-ui-3-audit.md, 27 screenshots): no money anywhere, three red n/a per Apple row, tuning split across two pages, 370 px of controls per card, Prove's 95 words and four zeros, Node's eleven numbers, the jobs table on every Updates page, fixes two taps away, no light mode, no layout under 900 px, developer lines on user surfaces. The design (docs/plans/miner-ui-3.md): four sections (Mine, Earnings, Prove, Settings); the card row carries the state word with its reason, MH/s, W with MH/W, £ a day at the user's electricity price, °C, Tune, the switch, and its details under a chevron (cap slider, identities, pin, telemetry, the tune table); the tune line per row (not tuned yet / tuning: step k of n with a bar / tuned N ago · point · next check <weekday> / measured only and why / pinned / stopped / fleet pause); Tune all; the goal (Efficiency, Balanced, Maximum) with its £ a day; Power control as one switch where it is the fix; the node as one line with Details; updates as one card; earnings money first, IGN second; proving as a tier sentence per card; every costly action confirmed in place (quit, change address, show key, trust mode), no dialogs; light and dark; a bottom tab bar under 720 px. The build: index.html, app.css, app.js rewritten on the same engine routes; the pure blocks keep their API and carry ember-tune's tuneNotice, tuneWord, toggle states and tune-line test (e9738dd) so the merge is clean; the update card is named Igneum Miner; the Rust side is untouched. node --test on the four UI files: 35 pass. Screenshots of the result: docs/plans/miner-ui-3/n00 to n26. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
|
@ -1,8 +1,9 @@
|
|||
/* Igneum Miner dashboard (miner-ui-2). The site's tokens (site/index.html): obsidian, graphite, ember, molten, bone,
|
||||
ash; Unbounded for headings, IBM Plex Sans for text, IBM Plex Mono for numbers and labels. Fonts ship in the binary.
|
||||
One type scale (--t-*), one spacing scale (--s-*), one colour set (:root), one accent (ember), used by every page.
|
||||
Layout: a rail on the left (six sections), a thin top bar, the status strip under it, the page in the middle, the
|
||||
log drawer at the bottom. The window lays out from 900 x 600 up (the hosts say the same minimum).
|
||||
/* Igneum Miner dashboard (miner-ui-3). The site's tokens (site/index.html): obsidian, graphite, ember, molten, bone,
|
||||
ash; Unbounded for the page title, the big button and the hero numbers; IBM Plex Sans for words; IBM Plex Mono for
|
||||
labels, units and small numbers. Fonts ship in the binary. One type scale (--t-*), one spacing scale (--s-*), one
|
||||
accent (ember). Dark by default, light under prefers-color-scheme or [data-theme=light]. Layout: a rail on the left
|
||||
(four sections), a thin top bar, the status strip under it, the page in the middle, the log drawer at the bottom.
|
||||
The window lays out from 900 x 600 (the hosts' minimum); under 720 px the rail becomes a bottom tab bar.
|
||||
Nothing here is newer than 2022 CSS (the WebView2 host). */
|
||||
@font-face{font-family:'IBM Plex Mono';font-style:normal;font-weight:400;font-display:swap;src:url(fonts/IBMPlexMono-400.woff2) format('woff2')}
|
||||
@font-face{font-family:'IBM Plex Mono';font-style:normal;font-weight:500;font-display:swap;src:url(fonts/IBMPlexMono-500.woff2) format('woff2')}
|
||||
|
|
@ -14,36 +15,42 @@
|
|||
@font-face{font-family:'Unbounded';font-style:normal;font-weight:900;font-display:swap;src:url(fonts/Unbounded-900.woff2) format('woff2')}
|
||||
|
||||
:root{
|
||||
/* colour */
|
||||
--obsidian:#0C0C0E;--graphite:#16161A;--line:#2A2A30;--line-2:#3A3A42;--ember:#F2541B;--ember-hi:#FF6A2B;--molten:#FFB35C;--bone:#F4F1EC;--ash:#9A9A9E;--ink-2:#C9C7C2;--ember-ink:#0C0C0E;
|
||||
--ember-12:rgba(242,84,27,.12);--ember-40:rgba(242,84,27,.4);--molten-10:rgba(255,179,92,.1);--molten-40:rgba(255,179,92,.4);--node-blue:#7FA7C9;--nvidia:#8BE37A;--rail-bg:#111114;
|
||||
/* colour, dark */
|
||||
--obsidian:#0C0C0E;--graphite:#16161A;--row:#111114;--line:#2A2A30;--line-2:#3A3A42;--ember:#F2541B;--ember-hi:#FF6A2B;--molten:#FFB35C;--bone:#F4F1EC;--ash:#9A9A9E;--ink-2:#C9C7C2;--ember-ink:#0C0C0E;
|
||||
--ember-12:rgba(242,84,27,.12);--ember-40:rgba(242,84,27,.4);--molten-10:rgba(255,179,92,.1);--molten-40:rgba(255,179,92,.4);--nvidia:#8BE37A;--rail-bg:#111114;--hover:rgba(255,255,255,.04);--top-bg:rgba(12,12,14,.86);--shadow:rgba(0,0,0,.6);--scrim:rgba(12,12,14,.72);--ok:#FFB35C;
|
||||
/* type scale */
|
||||
--t-xs:11px;--t-sm:12px;--t-base:13px;--t-md:14px;--t-lg:15px;--t-xl:16px;--t-2xl:18px;--t-num:26px;--t-h3:16px;--t-h2:32px;--t-h1:52px;
|
||||
--t-xs:11px;--t-sm:12px;--t-base:13px;--t-md:14px;--t-lg:15px;--t-xl:17px;--t-num:24px;--t-hero:34px;--t-h2:28px;--t-h1:44px;
|
||||
/* spacing scale */
|
||||
--s-1:4px;--s-2:8px;--s-3:12px;--s-4:16px;--s-5:20px;--s-6:28px;--s-7:40px;
|
||||
--gutter:var(--s-6);--card-pad:22px;--card-r:18px;--tile-pad:18px 20px;--tile-r:14px;--gap:var(--s-5);--gap-tile:var(--s-3);
|
||||
--s-1:4px;--s-2:8px;--s-3:12px;--s-4:16px;--s-5:24px;--s-6:32px;
|
||||
--gutter:var(--s-6);--card-pad:var(--s-5);--card-r:16px;--row-r:12px;--gap:var(--s-4);
|
||||
--sans:'IBM Plex Sans',system-ui,-apple-system,sans-serif;--mono:'IBM Plex Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;--head:'Unbounded',sans-serif;
|
||||
--top:60px;--bottom:0px;--drawer-h:260px;--rail:196px}
|
||||
@media (prefers-color-scheme:light){:root:not([data-theme="dark"]){
|
||||
--obsidian:#F4F1EC;--graphite:#FFFFFF;--row:#FAF8F5;--line:#E2DED8;--line-2:#CFCAC2;--ember:#E04A14;--ember-hi:#F2541B;--molten:#B8731F;--bone:#16161A;--ash:#6B6B70;--ink-2:#3C3C42;--ember-ink:#FFFFFF;
|
||||
--ember-12:rgba(224,74,20,.1);--ember-40:rgba(224,74,20,.4);--molten-10:rgba(184,115,31,.1);--molten-40:rgba(184,115,31,.4);--nvidia:#3C9B2C;--rail-bg:#EFEBE4;--hover:rgba(0,0,0,.04);--top-bg:rgba(244,241,236,.88);--shadow:rgba(0,0,0,.18);--scrim:rgba(244,241,236,.72);--ok:#B8731F}}
|
||||
:root[data-theme="light"]{
|
||||
--obsidian:#F4F1EC;--graphite:#FFFFFF;--row:#FAF8F5;--line:#E2DED8;--line-2:#CFCAC2;--ember:#E04A14;--ember-hi:#F2541B;--molten:#B8731F;--bone:#16161A;--ash:#6B6B70;--ink-2:#3C3C42;--ember-ink:#FFFFFF;
|
||||
--ember-12:rgba(224,74,20,.1);--ember-40:rgba(224,74,20,.4);--molten-10:rgba(184,115,31,.1);--molten-40:rgba(184,115,31,.4);--nvidia:#3C9B2C;--rail-bg:#EFEBE4;--hover:rgba(0,0,0,.04);--top-bg:rgba(244,241,236,.88);--shadow:rgba(0,0,0,.18);--scrim:rgba(244,241,236,.72);--ok:#B8731F}
|
||||
*{box-sizing:border-box}
|
||||
html,body{height:100%}
|
||||
body{margin:0;background:var(--obsidian);color:var(--bone);font-family:var(--sans);font-size:var(--t-lg);line-height:1.5;-webkit-font-smoothing:antialiased;overflow:hidden;user-select:none;-webkit-user-select:none;font-variant-numeric:tabular-nums}
|
||||
body{margin:0;background:var(--obsidian);color:var(--bone);font-family:var(--sans);font-size:var(--t-md);line-height:1.5;-webkit-font-smoothing:antialiased;overflow:hidden;user-select:none;-webkit-user-select:none;font-variant-numeric:tabular-nums}
|
||||
.mono,code,pre{font-family:var(--mono);font-variant-numeric:tabular-nums}
|
||||
.dim{color:var(--ash)}
|
||||
h1,h2,h3{font-family:var(--head);margin:0;line-height:1.1;text-wrap:balance}
|
||||
h1{font-weight:900;font-size:var(--t-h1);letter-spacing:-.01em}
|
||||
h2{font-weight:700;font-size:var(--t-h2)}
|
||||
h3{font-weight:700;font-size:var(--t-h3)}
|
||||
h3{font-weight:700;font-size:var(--t-xl)}
|
||||
p{margin:0}
|
||||
a{color:inherit}
|
||||
button{font:inherit;color:inherit}
|
||||
input,textarea{font-variant-numeric:tabular-nums}
|
||||
.eyebrow{font-family:var(--mono);font-size:var(--t-xs);letter-spacing:.18em;text-transform:uppercase;color:var(--ash);display:inline-flex;align-items:center;gap:var(--s-2);white-space:nowrap}
|
||||
.eyebrow{font-family:var(--mono);font-size:var(--t-xs);letter-spacing:.16em;text-transform:uppercase;color:var(--ash);display:inline-flex;align-items:center;gap:var(--s-2);white-space:nowrap}
|
||||
.eyebrow.ember{color:var(--ember)}
|
||||
[hidden]{display:none !important}
|
||||
::selection{background:rgba(242,84,27,.45)}
|
||||
|
||||
/* buttons */
|
||||
.btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--s-2);min-height:44px;padding:10px 20px;border-radius:10px;font-weight:600;font-size:var(--t-lg);border:1px solid var(--line-2);color:var(--bone);background:transparent;cursor:pointer;transition:transform .15s ease,background .15s ease,border-color .15s ease,opacity .15s ease,color .15s ease;white-space:nowrap}
|
||||
.btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--s-2);min-height:40px;padding:8px 18px;border-radius:10px;font-weight:600;font-size:var(--t-md);border:1px solid var(--line-2);color:var(--bone);background:transparent;cursor:pointer;transition:transform .15s ease,background .15s ease,border-color .15s ease,opacity .15s ease,color .15s ease;white-space:nowrap}
|
||||
.btn:hover{transform:translateY(-1px);border-color:var(--ash)}
|
||||
.btn:active{transform:none}
|
||||
.btn:disabled{opacity:.4;cursor:default;transform:none}
|
||||
|
|
@ -68,9 +75,9 @@ body.mac .rail-brand{padding-top:30px}
|
|||
.rail-nav{display:flex;flex-direction:column;gap:3px}
|
||||
.nav{position:relative;display:flex;align-items:center;gap:12px;width:100%;min-height:42px;padding:8px 12px;border:1px solid transparent;border-radius:11px;background:transparent;color:var(--ink-2);font-weight:500;font-size:var(--t-md);text-align:left;cursor:pointer;transition:background .15s ease,color .15s ease,border-color .15s ease}
|
||||
.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:rgba(255,255,255,.04);color:var(--bone)}
|
||||
.nav:hover{background:var(--hover);color:var(--bone)}
|
||||
.nav:hover svg{color:var(--ink-2)}
|
||||
.nav.on{background:var(--ember-12);border-color:rgba(242,84,27,.28);color:var(--bone);font-weight:600}
|
||||
.nav.on{background:var(--ember-12);border-color:var(--ember-40);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)}
|
||||
|
|
@ -85,7 +92,7 @@ body.mac .rail-brand{padding-top:30px}
|
|||
.rail-version{font-size:var(--t-xs);color:var(--ash);padding:8px 12px 0;letter-spacing:.06em}
|
||||
|
||||
/* top bar */
|
||||
.top{position:fixed;top:0;left:0;right:0;height:var(--top);display:flex;align-items:center;justify-content:space-between;gap:var(--s-4);padding:0 var(--gutter);background:rgba(12,12,14,.86);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border-bottom:1px solid rgba(42,42,48,.7);z-index:20;-webkit-app-region:drag}
|
||||
.top{position:fixed;top:0;left:0;right:0;height:var(--top);display:flex;align-items:center;justify-content:space-between;gap:var(--s-4);padding:0 var(--gutter);background:var(--top-bg);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border-bottom:1px solid var(--line);z-index:20;-webkit-app-region:drag}
|
||||
.top button,.top .pill{-webkit-app-region:no-drag}
|
||||
body.mac:not(.has-rail) .top{padding-left:92px}
|
||||
body.has-rail .top{left:var(--rail)}
|
||||
|
|
@ -96,21 +103,17 @@ body.has-rail .top{left:var(--rail)}
|
|||
.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}
|
||||
.top-status{font-size:var(--t-sm);color:var(--ash);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:380px}
|
||||
.top-status .pc+.pc::before{content:"·";margin:0 7px;color:var(--line-2)}
|
||||
.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)}
|
||||
.dot{width:8px;height:8px;border-radius:50%;background:var(--ash);display:inline-block;flex:0 0 8px}
|
||||
.dot.small{width:7px;height:7px;flex-basis:7px}
|
||||
.on .dot,.dot.live{background:var(--molten);animation:pulse 2s ease-in-out infinite}
|
||||
.warn .dot{background:var(--ember);animation:none}
|
||||
.warn .dot,.dot.bad{background:var(--ember);animation:none}
|
||||
@keyframes pulse{0%,100%{box-shadow:0 0 0 0 rgba(255,179,92,.5)}50%{box-shadow:0 0 0 7px rgba(255,179,92,0)}}
|
||||
@media (prefers-reduced-motion:reduce){.on .dot,.dot.live{animation:none}}
|
||||
|
||||
/* the status strip under the top bar (app.js, Notices): one notice at a time. It takes no room while empty; main's
|
||||
top moves once per change with a 150 ms transition (layoutStrip), never per poll. Tones: default molten (running,
|
||||
available, done), bad ember (failed, clock block, urgent). */
|
||||
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)}
|
||||
|
|
@ -124,11 +127,18 @@ body.has-rail .notices{left:var(--rail)}
|
|||
.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(255,255,255,.12);border-radius:2px;overflow:hidden;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 .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-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}
|
||||
|
||||
/* screens and pages */
|
||||
main{position:absolute;top:var(--top);bottom:0;left:0;right:0;overflow:auto;padding:0 var(--gutter);overscroll-behavior:contain;transition:top .15s ease}
|
||||
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}
|
||||
@media (prefers-reduced-motion:reduce){main{transition:none}}
|
||||
body.has-rail main{left:var(--rail)}
|
||||
body.drawer-open main{bottom:var(--drawer-h)}
|
||||
|
|
@ -140,15 +150,14 @@ body[data-phase="welcome"] #screen-welcome,body[data-phase="cards"] #screen-card
|
|||
.page{display:flex;flex-direction:column;gap:var(--gap);animation:rise .3s ease}
|
||||
|
||||
/* welcome */
|
||||
.hero{min-height:calc(100vh - var(--top));display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:var(--s-4);padding:var(--s-7) 0 48px}
|
||||
.coin-wrap{position:relative;width:148px;height:148px;margin-bottom:6px}
|
||||
.coin-wrap::before{content:"";position:absolute;inset:-40px;border-radius:50%;background:radial-gradient(circle,rgba(242,84,27,.28) 0,rgba(242,84,27,0) 65%);animation:breathe 4s ease-in-out infinite}
|
||||
.coin{position:relative;display:block;border-radius:50%;filter:drop-shadow(0 10px 30px rgba(242,84,27,.35))}
|
||||
.hero{min-height:calc(100vh - var(--top));display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:var(--s-4);padding:var(--s-6) 0 48px}
|
||||
.mark-wrap{position:relative;width:120px;height:120px;border-radius:26px;background:#0C0C0E;display:flex;align-items:center;justify-content:center;margin-bottom:6px;box-shadow:0 20px 50px rgba(242,84,27,.25)}
|
||||
.mark-wrap::before{content:"";position:absolute;inset:-40px;border-radius:50%;background:radial-gradient(circle,rgba(242,84,27,.28) 0,rgba(242,84,27,0) 65%);animation:breathe 4s ease-in-out infinite;z-index:-1}
|
||||
@keyframes breathe{0%,100%{opacity:.7;transform:scale(1)}50%{opacity:1;transform:scale(1.08)}}
|
||||
@media (prefers-reduced-motion:reduce){.coin-wrap::before{animation:none}}
|
||||
.lead{font-size:var(--t-2xl);color:var(--ink-2);max-width:54ch}
|
||||
.three{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--gap-tile);width:100%;max-width:860px;margin-top:10px;text-align:left}
|
||||
.tile{background:var(--graphite);border:1px solid var(--line);border-radius:var(--tile-r);padding:var(--tile-pad);display:flex;flex-direction:column;gap:6px;min-width:0}
|
||||
@media (prefers-reduced-motion:reduce){.mark-wrap::before{animation:none}}
|
||||
.lead{font-size:var(--t-xl);color:var(--ink-2);max-width:54ch}
|
||||
.three{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--s-3);width:100%;max-width:860px;margin-top:10px;text-align:left}
|
||||
.tile{background:var(--graphite);border:1px solid var(--line);border-radius:14px;padding:18px 20px;display:flex;flex-direction:column;gap:6px;min-width:0}
|
||||
.tile .k{font-family:var(--mono);font-size:var(--t-xs);letter-spacing:.14em;color:var(--ember)}
|
||||
.tile .t{font-family:var(--head);font-weight:700;font-size:var(--t-lg);line-height:1.25}
|
||||
.tile .s{font-size:var(--t-base);color:var(--ash);line-height:1.45}
|
||||
|
|
@ -162,6 +171,9 @@ body[data-phase="welcome"] #screen-welcome,body[data-phase="cards"] #screen-card
|
|||
.note{font-size:var(--t-base);color:var(--ash);line-height:1.5}
|
||||
.note.small{font-size:var(--t-sm);word-break:break-all}
|
||||
.help{font-size:var(--t-base);color:var(--ash);line-height:1.5;max-width:64ch}
|
||||
.line-text{font-size:var(--t-md);color:var(--ink-2);line-height:1.5}
|
||||
.line-text.ok{color:var(--molten)}
|
||||
.line-text.bad{color:var(--ember)}
|
||||
.cards{display:flex;flex-direction:column;gap:var(--s-3);margin-top:var(--s-2)}
|
||||
.card{background:var(--graphite);border:1px solid var(--line);border-radius:var(--card-r);padding:var(--card-pad);min-width:0}
|
||||
.card.detecting{display:flex;align-items:center;gap:18px}
|
||||
|
|
@ -169,11 +181,13 @@ body[data-phase="welcome"] #screen-welcome,body[data-phase="cards"] #screen-card
|
|||
.card.detecting .s{font-size:var(--t-sm);color:var(--ash);margin-top:var(--s-1)}
|
||||
.spinner{width:28px;height:28px;border-radius:50%;border:3px solid var(--line-2);border-top-color:var(--ember);animation:spin 1s linear infinite;flex:0 0 28px}
|
||||
@keyframes spin{to{transform:rotate(360deg)}}
|
||||
.top-gap{margin-top:var(--s-4)}
|
||||
.indent{margin-left:52px}
|
||||
|
||||
/* GPU rows (first run) */
|
||||
.gpu-row{display:flex;align-items:center;gap:var(--s-4);background:var(--graphite);border:1px solid var(--line);border-radius:var(--card-r);padding:16px 20px;min-width:0;flex-wrap:wrap}
|
||||
.gpu-row.off{opacity:.72}
|
||||
.badge{width:48px;height:48px;border-radius:13px;display:flex;align-items:center;justify-content:center;font-family:var(--mono);font-size:var(--t-xs);letter-spacing:.08em;flex:0 0 48px;border:1px solid var(--line-2);color:var(--molten);background:var(--obsidian)}
|
||||
.badge{width:44px;height:44px;border-radius:12px;display:flex;align-items:center;justify-content:center;font-family:var(--mono);font-size:var(--t-xs);letter-spacing:.08em;flex:0 0 44px;border:1px solid var(--line-2);color:var(--molten);background:var(--obsidian)}
|
||||
.badge.apple{color:var(--bone)}
|
||||
.badge.nvidia{color:var(--nvidia)}
|
||||
.badge.amd{color:var(--ember)}
|
||||
|
|
@ -183,17 +197,15 @@ body[data-phase="welcome"] #screen-welcome,body[data-phase="cards"] #screen-card
|
|||
.kind.discrete,.kind.apple{color:var(--molten);border-color:var(--molten-40)}
|
||||
.kind.external{color:var(--bone)}
|
||||
.gpu-row .meta{font-family:var(--mono);font-size:var(--t-sm);color:var(--ash);display:flex;flex-wrap:wrap;gap:4px 14px}
|
||||
.gpu-row .meta b{color:var(--ink-2);font-weight:500}
|
||||
.gpu-row .reason{font-size:var(--t-sm);color:var(--ash)}
|
||||
.gpu-row .msg{font-size:var(--t-sm);color:var(--ember)}
|
||||
.gpu-row .switch{padding:0;flex:0 0 auto;margin-left:auto}
|
||||
/* hot-plug (src/hotplug.rs): a removed card dims, a faulty one is named in ember, neither has live controls */
|
||||
.gpu-row.removed,.gpu-line.removed,.set-card.removed{opacity:.5}
|
||||
.gpu-row.unusable .name,.gpu-line.unusable .name,.set-card.unusable .name{color:var(--ember)}
|
||||
.gpu-row.removed,.gpu-line.removed{opacity:.5}
|
||||
.gpu-row.unusable .name,.gpu-line.unusable .name{color:var(--ember)}
|
||||
|
||||
/* the Mine page: the big switch and the three numbers */
|
||||
.hero-row{display:grid;grid-template-columns:1.3fr 1fr 1fr 1fr;gap:var(--gap-tile)}
|
||||
.toggle-big{display:flex;flex-direction:column;align-items:flex-start;justify-content:center;gap:4px;min-height:112px;padding:18px 20px;border-radius:var(--tile-r);border:1px solid var(--ember);background:var(--ember);color:var(--ember-ink);cursor:pointer;text-align:left;transition:background .15s ease,border-color .15s ease,transform .15s ease,color .15s ease;min-width:0}
|
||||
/* the Mine page: the big button and the live total */
|
||||
.hero-row{display:grid;grid-template-columns:minmax(220px,1fr) 2fr;gap:var(--s-3)}
|
||||
.toggle-big{display:flex;flex-direction:column;align-items:flex-start;justify-content:center;gap:4px;min-height:112px;padding:18px 20px;border-radius:14px;border:1px solid var(--ember);background:var(--ember);color:var(--ember-ink);cursor:pointer;text-align:left;transition:background .15s ease,border-color .15s ease,transform .15s ease,color .15s ease;min-width:0}
|
||||
.toggle-big:hover{background:var(--ember-hi);border-color:var(--ember-hi);transform:translateY(-1px)}
|
||||
.toggle-big:disabled{opacity:.45;cursor:default;transform:none}
|
||||
.toggle-big .ring{width:34px;height:34px;border-radius:50%;display:flex;align-items:center;justify-content:center;background:rgba(12,12,14,.18);margin-bottom:6px}
|
||||
|
|
@ -201,64 +213,50 @@ body[data-phase="welcome"] #screen-welcome,body[data-phase="cards"] #screen-card
|
|||
.toggle-big .tl{font-family:var(--head);font-weight:700;font-size:18px;line-height:1.15;white-space:nowrap}
|
||||
.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:#1b1b20}
|
||||
.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 .ts{color:var(--molten);opacity:1}
|
||||
.strip{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:var(--gap-tile)}
|
||||
.strip.three{grid-template-columns:repeat(3,minmax(0,1fr))}
|
||||
.cell{background:var(--graphite);border:1px solid var(--line);border-radius:var(--tile-r);padding:var(--tile-pad);display:flex;flex-direction:column;gap:6px;min-width:0}
|
||||
.cell .k{font-family:var(--mono);font-size:var(--t-xs);letter-spacing:.12em;text-transform:uppercase;color:var(--ash);white-space:nowrap}
|
||||
.cell .v{font-family:var(--head);font-weight:700;font-size:var(--t-num);line-height:1.1;font-variant-numeric:tabular-nums;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:flex;align-items:baseline;gap:var(--s-2);min-height:1.1em}
|
||||
.cell.ember .v{color:var(--ember)}
|
||||
.cell .v .unit{font-family:var(--mono);font-size:var(--t-sm);color:var(--ash);font-weight:500;letter-spacing:.08em}
|
||||
.cell .v.state{text-transform:capitalize;font-size:22px}
|
||||
.cell .v.small{font-size:18px}
|
||||
.cell .v.dim{color:var(--ash)}
|
||||
.cell .s{font-family:var(--mono);font-size:var(--t-sm);color:var(--ash);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.cell.ok .v.state{color:var(--molten)}
|
||||
.cell.bad .v.state{color:var(--ember)}
|
||||
.cell.bad .s{color:var(--ember)}
|
||||
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:var(--gap);align-items:start}
|
||||
.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)}
|
||||
.tot:first-child{padding-left:0;border-left:0}
|
||||
.tot .v{font-family:var(--head);font-weight:700;font-size:var(--t-hero);line-height:1.1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;letter-spacing:-.01em}
|
||||
.tot .v.ember{color:var(--ember)}
|
||||
.tot .v.ask-price{font-family:var(--sans);font-size:var(--t-md);font-weight:600;color:var(--molten);text-decoration:underline;text-underline-offset:3px;cursor:pointer;padding:9px 0 8px}
|
||||
.tot .k{font-family:var(--mono);font-size:var(--t-xs);letter-spacing:.14em;text-transform:uppercase;color:var(--ash);margin-top:4px}
|
||||
.tot .s{font-size:var(--t-sm);color:var(--ash);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:2px}
|
||||
.card-head{display:flex;justify-content:space-between;align-items:center;gap:var(--s-3);margin-bottom:var(--s-3);flex-wrap:wrap}
|
||||
.stats{display:flex;flex-wrap:wrap;gap:12px 16px;font-size:var(--t-sm);color:var(--ash)}
|
||||
.stats b{color:var(--bone);font-weight:500;font-variant-numeric:tabular-nums}
|
||||
#dag{display:block;width:100%;height:170px;border-radius:12px;background:var(--obsidian)}
|
||||
.legend{display:flex;flex-wrap:wrap;gap:14px 18px;margin-top:var(--s-3);font-size:var(--t-sm);color:var(--ink-2)}
|
||||
.legend span{display:inline-flex;align-items:center;gap:var(--s-2)}
|
||||
.sw{width:12px;height:12px;border-radius:3px;display:inline-block;border:1px solid var(--line-2)}
|
||||
.sw.ember{background:var(--ember);border-color:var(--ember)}
|
||||
.sw.glow{border-color:var(--molten);box-shadow:0 0 8px rgba(255,179,92,.7)}
|
||||
.sw.line{width:18px;height:0;border:0;border-top:1px dashed var(--line-2);border-radius:0}
|
||||
.head-right{display:flex;align-items:center;gap:var(--s-3);flex-wrap:wrap;min-width:0}
|
||||
.stats{font-size:var(--t-sm);color:var(--ash)}
|
||||
#dag{display:block;width:100%;height:110px;border-radius:10px;background:var(--obsidian);margin-bottom:var(--s-3)}
|
||||
.empty{color:var(--ash);font-size:var(--t-base);padding:10px 0}
|
||||
.empty.err{color:var(--ember)}
|
||||
.kv{display:flex;flex-direction:column}
|
||||
.kv>div{display:flex;justify-content:space-between;align-items:baseline;gap:var(--s-3);padding:7px 0;border-bottom:1px solid var(--line)}
|
||||
.kv>div{display:grid;grid-template-columns:120px auto 1fr;align-items:baseline;gap:var(--s-3);padding:7px 0;border-bottom:1px solid var(--line)}
|
||||
.kv>div:last-child{border-bottom:0}
|
||||
.kv .k{font-family:var(--mono);font-size:var(--t-xs);letter-spacing:.1em;text-transform:uppercase;color:var(--ash);white-space:nowrap}
|
||||
.kv .v{font-size:var(--t-md);font-variant-numeric:tabular-nums;color:var(--bone);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-align:right}
|
||||
.kv .v{font-size:var(--t-md);font-variant-numeric:tabular-nums;color:var(--bone);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.kv .v.ok{color:var(--molten)}
|
||||
.kv .v.warn{color:var(--ember)}
|
||||
.kv .v.dim{color:var(--ash)}
|
||||
.kv .m{font-size:var(--t-sm);color:var(--ash);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.card .note{margin-top:10px}
|
||||
.card .help+.help{margin-top:6px}
|
||||
.big-word{font-family:var(--head);font-weight:700;font-size:20px;line-height:1.2;margin:2px 0 8px;word-break:break-word}
|
||||
.big-word.ok{color:var(--molten)}
|
||||
.big-word.warn{color:var(--ember)}
|
||||
.big-word.dim{color:var(--ash)}
|
||||
.feed{display:flex;flex-direction:column;font-family:var(--mono);font-size:var(--t-sm);color:var(--ink-2);max-height:260px;overflow:auto}
|
||||
.feed>div{display:flex;justify-content:space-between;gap:10px;border-bottom:1px solid var(--line);padding:8px 0;align-items:baseline}
|
||||
.feed{display:flex;flex-direction:column;font-family:var(--mono);font-size:var(--t-sm);color:var(--ink-2)}
|
||||
.feed>div{display:flex;justify-content:space-between;gap:10px;border-bottom:1px solid var(--line);padding:7px 0;align-items:baseline}
|
||||
.feed>div:last-child{border-bottom:0}
|
||||
.feed>div>span:first-child{min-width: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{color:var(--ember)}
|
||||
.feed .k.warn{color:var(--ember)}
|
||||
.feed .k.block{color:var(--ember)}
|
||||
.feed .k.build{color:var(--ink-2)}
|
||||
.feed .k.error,.feed .k.warn,.feed .k.block{color:var(--ember)}
|
||||
.feed .k.build,.feed .k.info{color:var(--ash)}
|
||||
|
||||
/* the GPU rows on the Mine page: badge, name, numbers, the switch */
|
||||
/* 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 */
|
||||
.gpu-list{display:flex;flex-direction:column;gap:var(--s-2)}
|
||||
.gpu-line{display:grid;grid-template-columns:44px minmax(160px,1.4fr) repeat(3,minmax(84px,.7fr)) 48px;align-items:center;gap:var(--s-4);padding:12px 14px;border:1px solid var(--line);border-radius:var(--tile-r);background:var(--obsidian);min-width:0}
|
||||
.gpu-line{border:1px solid var(--line);border-radius:var(--row-r);background:var(--row);min-width:0}
|
||||
.gpu-line.off{opacity:.62}
|
||||
.gpu-line .badge{width:44px;height:44px;flex-basis:44px;border-radius:12px}
|
||||
.gpu-line.open{border-color:var(--line-2)}
|
||||
.gl-main{display:grid;grid-template-columns:44px minmax(150px,1.2fr) auto auto;align-items:center;gap:var(--s-4);padding:12px 14px}
|
||||
.gpu-line .who{min-width:0;display:flex;flex-direction:column;gap:3px}
|
||||
.gpu-line .name{font-family:var(--head);font-weight:700;font-size:var(--t-md);line-height:1.25;display:flex;align-items:center;gap:8px;flex-wrap:wrap}
|
||||
.gpu-line .st{display:inline-flex;align-items:center;gap:7px;font-family:var(--mono);font-size:var(--t-sm);color:var(--ash);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
|
||||
|
|
@ -267,46 +265,75 @@ body[data-phase="welcome"] #screen-welcome,body[data-phase="cards"] #screen-card
|
|||
.gpu-line .st i{width:7px;height:7px;border-radius:50%;background:currentColor;display:inline-block;flex:0 0 7px}
|
||||
.gpu-line .msg{font-size:var(--t-sm);color:var(--ember);line-height:1.4}
|
||||
.gpu-line .msg.dim{color:var(--ash)}
|
||||
.gpu-line .num{display:flex;flex-direction:column;gap:2px;min-width:0}
|
||||
.gpu-line .num .k{font-family:var(--mono);font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:var(--ash);white-space:nowrap}
|
||||
.gpu-line .num .v{font-family:var(--head);font-weight:700;font-size:18px;line-height:1.15;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:flex;align-items:baseline;gap:5px}
|
||||
.gpu-line .num .v .unit{font-family:var(--mono);font-size:10px;color:var(--ash);font-weight:500;letter-spacing:.08em}
|
||||
.gpu-line .num .v.hot{color:var(--ember)}
|
||||
.gpu-line .num .v.warm{color:var(--molten)}
|
||||
.gpu-line .num .v.na{color:var(--ash);font-weight:500;font-size:var(--t-md)}
|
||||
.nums{display:flex;gap:var(--s-5);justify-content:flex-end;align-items:flex-start;min-width:0;flex-wrap:wrap}
|
||||
.num{display:flex;flex-direction:column;gap:1px;min-width:56px;align-items:flex-end;text-align:right}
|
||||
.num .v{font-family:var(--head);font-weight:700;font-size:20px;line-height:1.15;white-space:nowrap;display:flex;align-items:baseline;gap:4px}
|
||||
.num .v .unit{font-family:var(--mono);font-size:10px;color:var(--ash);font-weight:500;letter-spacing:.08em}
|
||||
.num .k{font-family:var(--mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--ash);white-space:nowrap}
|
||||
.num .v.hot{color:var(--ember)}
|
||||
.num .v.warm{color:var(--molten)}
|
||||
.gpu-line.on .num.hash .v{color:var(--ember)}
|
||||
.gpu-line .switch{padding:0;justify-self:end}
|
||||
|
||||
/* the Settings page: per-card controls */
|
||||
.set-cards{display:flex;flex-direction:column;gap:var(--s-3);margin-bottom:var(--s-3)}
|
||||
.set-card{border:1px solid var(--line);border-radius:var(--tile-r);background:var(--obsidian);padding:14px 16px;display:flex;flex-direction:column;gap:10px;min-width:0}
|
||||
.set-card .head{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
|
||||
.set-card .head .name{font-family:var(--head);font-weight:700;font-size:var(--t-md)}
|
||||
.set-card .head .meta{font-family:var(--mono);font-size:var(--t-sm);color:var(--ash)}
|
||||
.set-card .ctl{display:grid;grid-template-columns:150px 1fr auto;align-items:center;gap:var(--s-3)}
|
||||
.set-card .ctl .k{font-size:var(--t-md);color:var(--bone);font-weight:500}
|
||||
.set-card .ctl .pv{font-family:var(--mono);font-size:var(--t-sm);color:var(--ink-2);min-width:110px;text-align:right;white-space:nowrap}
|
||||
.set-card input[type=range]{width:100%;accent-color:var(--ember);margin:0}
|
||||
.set-card .ctl .help{grid-column:1 / -1;margin-top:-4px}
|
||||
.set-card .line{font-family:var(--mono);font-size:var(--t-sm);color:var(--ash);display:flex;flex-wrap:wrap;gap:6px 12px;align-items:center}
|
||||
.set-card .line b{color:var(--ink-2);font-weight:500}
|
||||
.set-card .line.ok{color:var(--molten)}
|
||||
.set-card .line.hot{color:var(--ember)}
|
||||
.set-card .line.on{color:var(--molten)}
|
||||
.set-card .line .pin{font-family:var(--mono);font-size:var(--t-xs);letter-spacing:.08em;text-transform:uppercase;color:var(--ash);border:1px solid var(--line-2);border-radius:6px;padding:1px 6px}
|
||||
.acts{display:flex;align-items:center;gap:var(--s-3);justify-self:end}
|
||||
.acts .switch{padding:0}
|
||||
.chev-btn{width:30px;height:30px;border-radius:8px;border:1px solid transparent;background:transparent;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;color:var(--ash)}
|
||||
.chev-btn:hover{border-color:var(--line-2);color:var(--bone)}
|
||||
.chev{width:9px;height:9px;border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(45deg) translateY(-2px);transition:transform .15s ease;display:inline-block}
|
||||
.open .chev,.chev-btn.open .chev{transform:rotate(225deg) translateY(-2px)}
|
||||
.gl-tune{display:flex;align-items:center;gap:var(--s-3);flex-wrap:wrap;padding:8px 14px 10px 72px;font-family:var(--mono);font-size:var(--t-sm);color:var(--ink-2);border-top:1px solid var(--line)}
|
||||
.gl-tune .t{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
|
||||
.gl-tune .n{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.gl-tune.running{color:var(--molten)}
|
||||
.gl-tune.stopped,.gl-tune.paused{color:var(--ember)}
|
||||
.gl-tune .bar{flex-basis:100%;height:3px;border-radius:2px;background:var(--line);overflow:hidden;display:block}
|
||||
.gl-tune .bar b{display:block;height:100%;background:var(--molten);transition:width .5s linear}
|
||||
.gl-details{padding:12px 14px 14px 72px;border-top:1px solid var(--line);display:flex;flex-direction:column;gap:10px;user-select:text;-webkit-user-select:text}
|
||||
.gl-details .ctl{display:grid;grid-template-columns:120px 1fr auto;align-items:center;gap:var(--s-3)}
|
||||
.gl-details .ctl .k{font-size:var(--t-md);color:var(--bone);font-weight:500}
|
||||
.gl-details .ctl .pv{font-size:var(--t-sm);color:var(--ink-2);min-width:100px;text-align:right;white-space:nowrap}
|
||||
.gl-details input[type=range]{width:100%;accent-color:var(--ember);margin:0}
|
||||
.gl-details .ctl .help{grid-column:1 / -1;margin-top:-4px}
|
||||
.gl-details .ctl .help:first-of-type{grid-column:2}
|
||||
.gl-details .line{font-family:var(--mono);font-size:var(--t-sm);color:var(--ash);display:flex;flex-wrap:wrap;gap:6px 12px;align-items:center}
|
||||
.gl-details .line b{color:var(--ink-2);font-weight:500}
|
||||
.gl-details .line.ok{color:var(--molten)}
|
||||
.gl-details .line.hot{color:var(--ember)}
|
||||
.gl-details .line .pin{font-family:var(--mono);font-size:var(--t-xs);letter-spacing:.08em;text-transform:uppercase;color:var(--ash);border:1px solid var(--line-2);border-radius:6px;padding:1px 6px}
|
||||
.curve{font-size:var(--t-sm);font-family:var(--mono);border-collapse:collapse;width:100%}
|
||||
.curve th{text-align:left;font-weight:500;color:var(--ash);font-size:10px;letter-spacing:.1em;text-transform:uppercase;padding:3px 8px 3px 0}
|
||||
.curve td{padding:3px 8px 3px 0;border-top:1px solid var(--line);color:var(--ink-2)}
|
||||
.curve tr.marked td{color:var(--ash);text-decoration:line-through}
|
||||
.ids{display:flex;align-items:center;gap:6px;flex:0 0 auto;justify-self:end}
|
||||
.ids button{width:30px;height:30px;border-radius:8px;border:1px solid var(--line-2);background:transparent;color:var(--bone);cursor:pointer;font-size:16px;line-height:1}
|
||||
.ids button:hover{border-color:var(--ash)}
|
||||
.ids input{width:44px;text-align:center;background:var(--obsidian);border:1px solid var(--line-2);border-radius:8px;padding:5px 4px;color:var(--bone);font-family:var(--mono);font-size:var(--t-base);user-select:text;-webkit-user-select:text}
|
||||
.ids input:focus{outline:none;border-color:var(--ember)}
|
||||
.ids input::-webkit-inner-spin-button,.ids input::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}
|
||||
.lead-card .lead-row,.lead-row{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--s-4)}
|
||||
.lead-text{min-width:0;display:flex;flex-direction:column;gap:6px}
|
||||
.lead-text h3{font-size:var(--t-2xl)}
|
||||
|
||||
/* the tuning card: the goal, its consequence, the schedule, Power control when it is the fix */
|
||||
.goal-row{display:flex;align-items:center;gap:var(--s-4);flex-wrap:wrap;margin-bottom:var(--s-3)}
|
||||
.seg{display:inline-flex;background:var(--obsidian);border:1px solid var(--line);border-radius:10px;padding:3px;gap:2px}
|
||||
.seg-b{border:0;background:transparent;color:var(--ash);font-size:var(--t-base);font-weight:500;padding:6px 14px;border-radius:8px;cursor:pointer;transition:background .15s ease,color .15s ease;white-space:nowrap}
|
||||
.seg-b:hover{color:var(--bone)}
|
||||
.seg-b.on{background:var(--graphite);color:var(--bone);box-shadow:0 1px 3px rgba(0,0,0,.25);font-weight:600}
|
||||
.goal-line{font-size:var(--t-base);color:var(--ink-2);min-width:0}
|
||||
|
||||
/* the node line and the disclosures */
|
||||
.disclose{display:flex;align-items:center;justify-content:space-between;gap:var(--s-3);width:100%;border:0;background:transparent;padding:0;cursor:pointer;text-align:left;color:var(--bone);font-size:var(--t-md);font-weight:500;min-height:32px}
|
||||
.disclose:hover{color:var(--bone)}
|
||||
.disclose .chev{color:var(--ash);flex:0 0 auto;margin-right:4px}
|
||||
.disclose-right{display:flex;align-items:center;gap:var(--s-3);min-width:0}
|
||||
.disclose-right .dim{font-size:var(--t-sm);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0;max-width:52ch}
|
||||
.node-line{display:inline-flex;align-items:center;gap:10px;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.node-card.bad .node-line{color:var(--ember)}
|
||||
.card .disclose+.disclose,.card .details+.disclose,.card .help+.disclose,.card .addr-big+.help+.disclose{margin-top:var(--s-3);padding-top:var(--s-3);border-top:1px solid var(--line)}
|
||||
.details{padding-top:var(--s-3);display:flex;flex-direction:column;gap:var(--s-3)}
|
||||
.switch-list{display:flex;flex-direction:column;gap:3px;font-size:var(--t-sm);color:var(--ash);margin-top:8px}
|
||||
.switch-list span{display:flex;justify-content:space-between;gap:12px}
|
||||
.switch-list span.past{opacity:.55}
|
||||
.switch-list span.next{color:var(--molten)}
|
||||
.lead-row{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--s-4)}
|
||||
.lead-text{min-width:0;display:flex;flex-direction:column;gap:6px}
|
||||
.lead-text h3{font-size:var(--t-xl)}
|
||||
.addr-big{display:flex;align-items:center;gap:12px;background:var(--obsidian);border:1px solid var(--line-2);border-radius:12px;padding:14px 16px;font-size:var(--t-lg);word-break:break-all;user-select:text;-webkit-user-select:text;margin-bottom:10px}
|
||||
.addr-big span{flex:1;min-width:0;color:var(--molten)}
|
||||
.card.adv{padding:0}
|
||||
|
|
@ -317,22 +344,35 @@ body[data-phase="welcome"] #screen-welcome,body[data-phase="cards"] #screen-card
|
|||
.card.adv summary .eyebrow{margin-left:0}
|
||||
.card.adv>:not(summary){margin-left:var(--card-pad);margin-right:var(--card-pad)}
|
||||
.card.adv>:last-child{margin-bottom:var(--card-pad)}
|
||||
.job-history table{margin-top:var(--s-2)}
|
||||
table{border-collapse:collapse;width:100%;font-size:var(--t-base)}
|
||||
.job-history th{text-align:left;font-weight:500;color:var(--ash);font-size:var(--t-xs);padding:4px 6px 4px 0;font-family:var(--mono);letter-spacing:.1em;text-transform:uppercase}
|
||||
.job-history td{padding:6px 6px 6px 0;border-top:1px solid var(--line);vertical-align:top}
|
||||
.job-history tr.failed td,.job-history tr.timeout td,.job-history tr.aborted td{color:var(--ember)}
|
||||
.job-history tr.running td{color:var(--molten)}
|
||||
.clock-card{border:1px solid rgba(242,84,27,.5);background:rgba(242,84,27,.08);border-radius:12px;padding:12px 14px;display:flex;flex-direction:column;gap:var(--s-2)}
|
||||
.clock-card.warn{border-color:var(--molten-40);background:rgba(255,179,92,.06)}
|
||||
#s-jobs-history th{text-align:left;font-weight:500;color:var(--ash);font-size:var(--t-xs);padding:4px 6px 4px 0;font-family:var(--mono);letter-spacing:.1em;text-transform:uppercase}
|
||||
#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-msg{font-size:var(--t-md);color:var(--bone);line-height:1.45}
|
||||
.clock-card .note{margin-top:0}
|
||||
|
||||
/* earnings: money first */
|
||||
.money{display:flex;flex-direction:column;margin-bottom:var(--s-2)}
|
||||
.money-row{display:flex;align-items:baseline;gap:var(--s-3);padding:8px 0;border-bottom:1px solid var(--line);flex-wrap:wrap}
|
||||
.money-row:last-child{border-bottom:0}
|
||||
.money-row .v{font-family:var(--head);font-weight:700;font-size:var(--t-hero);line-height:1.1;white-space:nowrap;letter-spacing:-.01em;min-width:200px}
|
||||
.money-row .v.small{font-size:var(--t-num)}
|
||||
.money-row .s{font-size:var(--t-base);color:var(--ash);min-width:0}
|
||||
|
||||
/* prove */
|
||||
.tier-list{list-style:none;margin:var(--s-3) 0 0;padding:0;display:flex;flex-direction:column;gap:6px;font-size:var(--t-md);color:var(--ink-2)}
|
||||
.tier-list li{display:flex;gap:10px;align-items:baseline}
|
||||
.tier-list li::before{content:"";width:6px;height:6px;border-radius:50%;background:var(--ember);flex:0 0 6px;position:relative;top:-2px}
|
||||
#pv-line{margin-top:var(--s-3)}
|
||||
|
||||
/* address options */
|
||||
.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:#1A1614}
|
||||
.option.on{border-color:var(--ember);background:var(--ember-12)}
|
||||
.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)}
|
||||
|
|
@ -342,15 +382,15 @@ table{border-collapse:collapse;width:100%;font-size:var(--t-base)}
|
|||
.option .s{font-size:var(--t-md);color:var(--ash);line-height:1.5}
|
||||
.tag{font-family:var(--mono);font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--molten);border:1px solid var(--molten-40);border-radius:999px;padding:2px 8px}
|
||||
.addr-input{width:100%;margin-top:var(--s-2);background:var(--obsidian);border:1px solid var(--line-2);border-radius:10px;padding:12px 14px;color:var(--bone);font-size:var(--t-md);letter-spacing:.02em;user-select:text;-webkit-user-select:text}
|
||||
.addr-input.short{width:100px;flex:0 0 100px}
|
||||
.addr-input:focus{outline:none;border-color:var(--ember)}
|
||||
.option:not(.on) .addr-input{display:none}
|
||||
.err{font-size:var(--t-base);color:var(--ember)}
|
||||
|
||||
/* the update card (app.js, UpdateCard; the wallet carries the same block): the mark with its ring, the name, one
|
||||
line, up to three note lines, the size, Install now and Later. Over the key sheet and the pages. */
|
||||
.upd-wrap{position:fixed;inset:0;z-index:35;background:rgba(12,12,14,.72);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);display:flex;align-items:center;justify-content:center;padding:24px;animation:fade .25s ease}
|
||||
/* the update card (app.js, UpdateCard; the wallet carries the same block) */
|
||||
.upd-wrap{position:fixed;inset:0;z-index:35;background:var(--scrim);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);display:flex;align-items:center;justify-content:center;padding:24px;animation:fade .25s ease}
|
||||
@keyframes fade{from{opacity:0}to{opacity:1}}
|
||||
.upd-card{position:relative;width:100%;max-width:500px;max-height:calc(100vh - 48px);overflow:auto;background:var(--graphite);border:1px solid var(--line-2);border-radius:22px;padding:34px 32px 28px;display:flex;flex-direction:column;align-items:center;text-align:center;gap:var(--s-2);box-shadow:0 30px 80px rgba(0,0,0,.6),0 0 0 1px rgba(242,84,27,.08);animation:rise .3s ease;outline:none}
|
||||
.upd-card{position:relative;width:100%;max-width:500px;max-height:calc(100vh - 48px);overflow:auto;background:var(--graphite);border:1px solid var(--line-2);border-radius:22px;padding:34px 32px 28px;display:flex;flex-direction:column;align-items:center;text-align:center;gap:var(--s-2);box-shadow:0 30px 80px var(--shadow),0 0 0 1px rgba(242,84,27,.08);animation:rise .3s ease;outline:none}
|
||||
.upd-card::before{content:"";position:absolute;left:50%;top:-80px;width:360px;height:260px;transform:translateX(-50%);border-radius:50%;background:radial-gradient(circle,rgba(242,84,27,.22) 0,rgba(242,84,27,0) 62%);pointer-events:none}
|
||||
.upd-mark{position:relative;width:96px;height:96px;display:flex;align-items:center;justify-content:center;margin-bottom:var(--s-3)}
|
||||
.upd-mark img{position:relative;display:block;filter:drop-shadow(0 6px 18px rgba(242,84,27,.35))}
|
||||
|
|
@ -381,8 +421,8 @@ table{border-collapse:collapse;width:100%;font-size:var(--t-base)}
|
|||
@media (max-height:620px){.upd-card{padding:24px 24px 20px}.upd-mark{width:72px;height:72px;margin-bottom:var(--s-2)}.upd-mark img{width:32px;height:32px}.upd-name{font-size:20px}}
|
||||
|
||||
/* sheet (the key) */
|
||||
.sheet-wrap{position:fixed;inset:0;z-index:30;background:rgba(12,12,14,.72);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);display:flex;align-items:center;justify-content:center;padding:24px}
|
||||
.sheet{background:var(--graphite);border:1px solid var(--line-2);border-radius:22px;padding:32px;max-width:640px;width:100%;max-height:calc(100vh - 48px);overflow:auto;display:flex;flex-direction:column;gap:14px;box-shadow:0 30px 80px rgba(0,0,0,.6);animation:rise .3s ease}
|
||||
.sheet-wrap{position:fixed;inset:0;z-index:30;background:var(--scrim);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);display:flex;align-items:center;justify-content:center;padding:24px}
|
||||
.sheet{background:var(--graphite);border:1px solid var(--line-2);border-radius:22px;padding:32px;max-width:640px;width:100%;max-height:calc(100vh - 48px);overflow:auto;display:flex;flex-direction:column;gap:14px;box-shadow:0 30px 80px var(--shadow);animation:rise .3s ease}
|
||||
.sheet .sub{font-size:var(--t-lg);color:var(--ink-2)}
|
||||
.field{display:flex;flex-direction:column;gap:var(--s-2);margin-top:6px}
|
||||
.field .k{font-family:var(--mono);font-size:var(--t-xs);letter-spacing:.12em;text-transform:uppercase;color:var(--ash)}
|
||||
|
|
@ -398,13 +438,13 @@ table{border-collapse:collapse;width:100%;font-size:var(--t-base)}
|
|||
|
||||
/* rows, switches */
|
||||
.row{display:flex;gap:10px;align-items:center;min-width:0}
|
||||
.row.between{justify-content:space-between}
|
||||
.row.wrap{flex-wrap:wrap}
|
||||
.row .addr-input{margin-top:0;min-width:0}
|
||||
.switch{display:flex;align-items:center;gap:var(--s-3);font-size:var(--t-md);cursor:pointer;padding:8px 0 2px;line-height:1.35}
|
||||
.switch{display:flex;align-items:flex-start;gap:var(--s-3);font-size:var(--t-md);cursor:pointer;padding:9px 0;line-height:1.4}
|
||||
.switch+.switch{border-top:1px solid var(--line)}
|
||||
.switch input{position:absolute;opacity:0;width:0;height:0}
|
||||
.switch .track{width:40px;height:22px;border-radius:999px;background:var(--line-2);position:relative;flex:0 0 40px;transition:background .15s ease}
|
||||
.switch .track::after{content:"";position:absolute;top:3px;left:3px;width:16px;height:16px;border-radius:50%;background:var(--bone);transition:transform .15s ease}
|
||||
.switch .track{width:40px;height:22px;border-radius:999px;background:var(--line-2);position:relative;flex:0 0 40px;transition:background .15s ease;margin-top:1px}
|
||||
.switch .track::after{content:"";position:absolute;top:3px;left:3px;width:16px;height:16px;border-radius:50%;background:#F4F1EC;transition:transform .15s ease}
|
||||
.switch input:checked+.track{background:var(--ember)}
|
||||
.switch input:checked+.track::after{transform:translateX(18px)}
|
||||
.switch input:focus-visible+.track{outline:2px solid var(--ember);outline-offset:3px}
|
||||
|
|
@ -412,10 +452,12 @@ table{border-collapse:collapse;width:100%;font-size:var(--t-base)}
|
|||
.switch.lg .track{width:52px;height:30px;flex-basis:52px}
|
||||
.switch.lg .track::after{width:24px;height:24px}
|
||||
.switch.lg input:checked+.track::after{transform:translateX(22px)}
|
||||
.switch+.help{margin-bottom:6px}
|
||||
.sw-text{min-width:0}
|
||||
.sw-text b{font-weight:600}
|
||||
.sw-text .dim{font-size:var(--t-base)}
|
||||
|
||||
/* log drawer */
|
||||
.drawer{position:fixed;left:0;right:0;bottom:0;height:0;overflow:hidden;background:var(--obsidian);border-top:1px solid var(--line);z-index:21;transition:height .2s ease;display:flex;flex-direction:column;box-shadow:0 -12px 30px rgba(0,0,0,.35)}
|
||||
.drawer{position:fixed;left:0;right:0;bottom:0;height:0;overflow:hidden;background:var(--obsidian);border-top:1px solid var(--line);z-index:21;transition:height .2s ease;display:flex;flex-direction:column;box-shadow:0 -12px 30px var(--shadow)}
|
||||
body.has-rail .drawer{left:var(--rail)}
|
||||
body.drawer-open .drawer{height:var(--drawer-h)}
|
||||
body.drawer-drag .drawer{transition:none}
|
||||
|
|
@ -450,8 +492,8 @@ body.drawer-drag main{pointer-events:none}
|
|||
.log-ruler .t1{right:0;border-radius:0 0 0 6px}
|
||||
.log-ruler .mk{position:absolute;top:4px;bottom:4px;width:2px;margin-left:-1px;background:var(--ember);opacity:.9;pointer-events:none}
|
||||
.log-ruler .mk.swap{background:var(--molten)}
|
||||
.log-ruler .win{position:absolute;top:0;bottom:0;background:rgba(244,241,236,.14);border-left:1px solid rgba(244,241,236,.35);border-right:1px solid rgba(244,241,236,.35);min-width:3px;pointer-events:none}
|
||||
.log-ruler:hover .win{background:rgba(244,241,236,.2)}
|
||||
.log-ruler .win{position:absolute;top:0;bottom:0;background:rgba(127,127,127,.14);border-left:1px solid rgba(127,127,127,.35);border-right:1px solid rgba(127,127,127,.35);min-width:3px;pointer-events:none}
|
||||
.log-ruler:hover .win{background:rgba(127,127,127,.2)}
|
||||
.log-view{position:relative;flex:1;overflow:auto;overscroll-behavior:contain;user-select:text;-webkit-user-select:text;contain:strict}
|
||||
.log-pad{position:relative;min-height:100%}
|
||||
.log{margin:0;position:absolute;top:0;left:0;right:0;padding:6px var(--gutter);font-size:var(--t-sm);line-height:19px;color:var(--ink-2);white-space:pre;will-change:transform}
|
||||
|
|
@ -459,7 +501,7 @@ body.drawer-drag main{pointer-events:none}
|
|||
.log-view.wrap .log{position:relative;padding-bottom:12px}
|
||||
.log-view.wrap .log .ln{height:auto;white-space:pre-wrap;word-break:break-all;overflow:visible}
|
||||
.log .src{color:var(--ash)}
|
||||
.log .src.node{color:var(--node-blue)}
|
||||
.log .src.node{color:#7FA7C9}
|
||||
.log .src.miner1,.log .src.miner2,.log .src.miner3,.log .src.miner4,.log .src.miner5,.log .src.miner6,.log .src.miner7,.log .src.miner8{color:var(--molten)}
|
||||
.log .src.app{color:var(--ember)}
|
||||
.log .src.watch{color:var(--ash)}
|
||||
|
|
@ -467,22 +509,18 @@ body.drawer-drag main{pointer-events:none}
|
|||
.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-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 rgba(0,0,0,.5);z-index:2;gap:6px;padding:6px 12px}
|
||||
.log-jump:hover{background:#1c1a18;border-color:var(--molten)}
|
||||
.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)}
|
||||
|
||||
.toast{position:fixed;left:50%;bottom:16px;transform:translateX(-50%);background:var(--graphite);border:1px solid var(--line-2);border-radius:10px;padding:10px 16px;font-size:var(--t-base);z-index:40;box-shadow:0 10px 30px rgba(0,0,0,.5);max-width:min(90vw,520px);text-align:center}
|
||||
.toast{position:fixed;left:50%;bottom:16px;transform:translateX(-50%);background:var(--graphite);border:1px solid var(--line-2);border-radius:10px;padding:10px 16px;font-size:var(--t-base);z-index:40;box-shadow:0 10px 30px var(--shadow);max-width:min(90vw,520px);text-align:center}
|
||||
body.has-rail .toast{left:calc(50% + var(--rail) / 2)}
|
||||
body.drawer-open .toast{bottom:calc(var(--drawer-h) + 16px)}
|
||||
|
||||
/* narrow windows (the 900 px floor): the rail folds to icons with small labels, the hero row and the strips fold to
|
||||
two columns, the two-column grid to one, the GPU rows drop a number */
|
||||
/* narrow windows: 1180 the hero wraps; 1000 the rail folds to icons; 900 the row's cells wrap; 720 a bottom tab bar */
|
||||
@media (max-width:1180px){
|
||||
.hero-row{grid-template-columns:1fr 1fr}
|
||||
.toggle-big{grid-row:span 1}
|
||||
.strip{grid-template-columns:repeat(2,minmax(0,1fr))}
|
||||
.strip.three{grid-template-columns:repeat(3,minmax(0,1fr))}
|
||||
.grid2{grid-template-columns:1fr}
|
||||
.top-status{max-width:220px}
|
||||
.hero-row{grid-template-columns:1fr}
|
||||
.totals{grid-template-columns:repeat(4,minmax(0,1fr))}
|
||||
.nums{gap:var(--s-4)}
|
||||
}
|
||||
@media (max-width:1000px){
|
||||
:root{--rail:76px;--gutter:var(--s-5)}
|
||||
|
|
@ -497,29 +535,73 @@ body.drawer-open .toast{bottom:calc(var(--drawer-h) + 16px)}
|
|||
.rail-status{display:none}
|
||||
.rail-version{text-align:center;padding:8px 0 0;font-size:10px;white-space:nowrap}
|
||||
.rail-version .chain{display:none}
|
||||
.gpu-line{grid-template-columns:44px minmax(140px,1.4fr) repeat(2,minmax(80px,.7fr)) 48px}
|
||||
.gpu-line .num.power{display:none}
|
||||
.page-sub{display:none}
|
||||
.top-status{display:none}
|
||||
.strip.three{grid-template-columns:repeat(2,minmax(0,1fr))}
|
||||
.set-card .ctl{grid-template-columns:120px 1fr auto}
|
||||
.gl-main{grid-template-columns:44px minmax(140px,1fr) auto}
|
||||
.nums{grid-column:2;justify-content:flex-start}
|
||||
.acts{grid-column:3;grid-row:1}
|
||||
.gl-tune,.gl-details{padding-left:14px}
|
||||
.kv>div{grid-template-columns:110px auto 1fr}
|
||||
}
|
||||
@media (max-width:860px){
|
||||
.three{grid-template-columns:1fr}
|
||||
h1{font-size:40px}
|
||||
.totals{grid-template-columns:repeat(2,minmax(0,1fr));row-gap:var(--s-3)}
|
||||
.tot:nth-child(3){padding-left:0;border-left:0}
|
||||
.money-row .v{min-width:0}
|
||||
}
|
||||
/* short windows (the 600 px floor): tighter paddings, a lower hero, a smaller canvas, so the drawer always has room */
|
||||
@media (max-width:720px){
|
||||
:root{--rail:0px;--gutter:var(--s-4);--bottom:64px}
|
||||
body.has-rail .rail{top:auto;bottom:0;left:0;right:0;width:auto;height:64px;flex-direction:row;align-items:center;border-right:0;border-top:1px solid var(--line);padding:0 var(--s-2)}
|
||||
.rail-brand{display:none}
|
||||
.rail-nav{flex-direction:row;flex:1;gap:0;justify-content:space-around}
|
||||
.nav{min-height:56px;padding:6px 2px;font-size:10px;border-radius:10px;flex:1;max-width:120px}
|
||||
.nav.on::before{display:none}
|
||||
.rail-foot{margin-top:0;padding-top:0;border-top:0;flex-direction:row;gap:0}
|
||||
.rail-foot .nav.small{min-height:56px;flex:0 0 auto;padding:6px 8px}
|
||||
.rail-version{display:none}
|
||||
body.has-rail .top,body.has-rail .notices,body.has-rail main,body.has-rail .drawer,body.has-rail .ask-wrap{left:0}
|
||||
body.has-rail main{bottom:var(--bottom)}
|
||||
body.has-rail .toast{left:50%;bottom:calc(var(--bottom) + 12px)}
|
||||
body.drawer-open .drawer{bottom:var(--bottom)}
|
||||
.ask-wrap{bottom:var(--bottom)}
|
||||
.top{padding:0 var(--s-4)}
|
||||
.page-title h1{font-size:17px}
|
||||
.pill{min-width:0}
|
||||
.hero-row{grid-template-columns:1fr}
|
||||
.toggle-big{min-height:88px}
|
||||
.gl-main{grid-template-columns:44px 1fr;gap:var(--s-3);padding:12px}
|
||||
.nums{grid-column:1 / -1;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--s-2) var(--s-3);justify-items:start}
|
||||
.acts{grid-column:1 / -1;grid-row:auto;justify-self:end}
|
||||
.gpu-line .name{font-size:var(--t-base)}
|
||||
.num{align-items:flex-start;text-align:left}
|
||||
.acts{gap:var(--s-2)}
|
||||
.gl-tune,.gl-details{padding-left:12px;padding-right:12px}
|
||||
.gl-details .ctl{grid-template-columns:1fr;gap:6px}
|
||||
.gl-details .ctl .help:first-of-type{grid-column:1}
|
||||
.ids{justify-self:start}
|
||||
.kv>div{grid-template-columns:96px 1fr;gap:4px var(--s-3)}
|
||||
.kv .m{grid-column:1 / -1;white-space:normal}
|
||||
.goal-row{align-items:flex-start;flex-direction:column}
|
||||
.seg{width:100%}
|
||||
.seg-b{flex:1;padding:6px 8px}
|
||||
.indent{margin-left:0}
|
||||
.disclose-right .dim{display:none}
|
||||
.lead-row{flex-direction:column}
|
||||
.step{padding:32px 0}
|
||||
.money-row .v{font-size:var(--t-num)}
|
||||
.drawer-head{padding-left:var(--s-4);padding-right:var(--s-4)}
|
||||
.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 */
|
||||
@media (max-height:700px){
|
||||
:root{--card-pad:18px;--tile-pad:14px 16px;--gap:var(--s-4)}
|
||||
:root{--card-pad:18px;--gap:var(--s-3)}
|
||||
#screen-dashboard{padding:16px 0 20px}
|
||||
#dag{height:140px}
|
||||
#dag{height:90px}
|
||||
.hero{gap:var(--s-3);padding:var(--s-5) 0 var(--s-6)}
|
||||
.coin-wrap{width:104px;height:104px}
|
||||
.coin{width:104px;height:104px}
|
||||
.mark-wrap{width:88px;height:88px}
|
||||
h1{font-size:40px}
|
||||
.lead{font-size:var(--t-xl)}
|
||||
.lead{font-size:var(--t-lg)}
|
||||
.step{padding:32px 0 32px}
|
||||
.feed{max-height:200px}
|
||||
.drawer-head{padding-bottom:6px}
|
||||
.toggle-big{min-height:96px}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,13 +4,14 @@
|
|||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Igneum Miner</title>
|
||||
<meta name="color-scheme" content="dark">
|
||||
<meta name="color-scheme" content="dark light">
|
||||
<link rel="icon" href="mark.svg" type="image/svg+xml">
|
||||
<link rel="stylesheet" href="app.css">
|
||||
</head>
|
||||
<body class="phase-welcome" data-phase="welcome" data-page="mine">
|
||||
|
||||
<!-- the rail: one button per section (miner-ui-2). Shown on the dashboard; the setup screens have no rail. -->
|
||||
<!-- the rail: four sections (miner-ui-3: Mine, Earnings, Prove, Settings), Logs and Quit in the foot. Under 720 px it
|
||||
is a bottom tab bar. The setup screens have no rail. -->
|
||||
<aside class="rail" id="rail" hidden>
|
||||
<div class="rail-brand">
|
||||
<img src="mark.svg" width="28" height="28" alt="">
|
||||
|
|
@ -18,11 +19,9 @@
|
|||
</div>
|
||||
<nav class="rail-nav" id="rail-nav" aria-label="Sections">
|
||||
<button class="nav on" data-page="mine" aria-current="page"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M13 2 4 14h7l-1 8 9-12h-7l1-8z"/></svg><span>Mine</span></button>
|
||||
<button class="nav" data-page="earnings"><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="3" y="6" width="18" height="13" rx="2"/><path d="M3 10h18M16 15h2"/></svg><span>Earnings</span></button>
|
||||
<button class="nav" data-page="prove"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2 4 5v6c0 5 3.4 9.4 8 11 4.6-1.6 8-6 8-11V5l-8-3z"/><path d="m9 12 2 2 4-4"/></svg><span>Prove</span></button>
|
||||
<button class="nav" data-page="rewards"><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="3" y="6" width="18" height="13" rx="2"/><path d="M3 10h18M16 15h2"/></svg><span>Rewards</span></button>
|
||||
<button class="nav" data-page="node"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3c3 3.5 3 14.5 0 18M12 3c-3 3.5-3 14.5 0 18"/></svg><span>Node</span></button>
|
||||
<button class="nav" data-page="updates"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 4v11M7 10l5 5 5-5"/><path d="M4 19h16"/></svg><span>Updates</span><i class="nav-dot" id="nav-updates-dot" hidden></i></button>
|
||||
<button class="nav" data-page="settings"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7h10M18 7h2M4 17h4M12 17h8"/><circle cx="16" cy="7" r="2"/><circle cx="10" cy="17" r="2"/></svg><span>Settings</span></button>
|
||||
<button class="nav" data-page="settings"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7h10M18 7h2M4 17h4M12 17h8"/><circle cx="16" cy="7" r="2"/><circle cx="10" cy="17" r="2"/></svg><span>Settings</span><i class="nav-dot" id="nav-updates-dot" hidden></i></button>
|
||||
</nav>
|
||||
<div class="rail-foot">
|
||||
<div class="rail-status mono" id="rail-status"></div>
|
||||
|
|
@ -42,13 +41,11 @@
|
|||
<span class="page-sub" id="page-sub"></span>
|
||||
</div>
|
||||
<div class="top-right">
|
||||
<span class="top-status mono" id="top-status"></span>
|
||||
<div class="pill" id="pill"><span class="dot"></span><span id="pill-text">starting</span></div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- the status strip: one notice at a time (updates, remote jobs, the clock), the most important first; app.js fills
|
||||
it from the state (Notices) and the content below moves once when it appears or goes. -->
|
||||
<!-- the status strip: one notice at a time (app.js, Notices) -->
|
||||
<div class="notices" id="notices" hidden>
|
||||
<div class="notice" id="notice" role="status" aria-live="polite">
|
||||
<span class="notice-text" id="notice-text"></span>
|
||||
|
|
@ -59,36 +56,45 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- the quit question, in place of a dialog (the viewer never shows one) -->
|
||||
<div class="ask-wrap" id="ask-quit" hidden>
|
||||
<div class="ask">
|
||||
<span class="ask-text">Quit Igneum Miner? Mining stops first, then the node.</span>
|
||||
<button class="btn small primary" id="ask-quit-yes">Quit</button>
|
||||
<button class="btn small ghost" id="ask-quit-no">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<main id="main">
|
||||
|
||||
<!-- 1. welcome -->
|
||||
<section class="screen" id="screen-welcome">
|
||||
<div class="hero">
|
||||
<div class="coin-wrap"><img class="coin" src="coin.png" width="148" height="148" alt=""></div>
|
||||
<div class="mark-wrap"><img src="mark.svg" width="72" height="72" alt=""></div>
|
||||
<div class="eyebrow ember">devnet v4 · nothing is bought or sold</div>
|
||||
<h1>Igneum Miner</h1>
|
||||
<p class="lead">Mined by GPUs. Proven by fire. This app runs a node on this machine and mines Igneum with your graphics card.</p>
|
||||
<p class="lead">Runs a node and mines Igneum with your graphics card. Set up in two steps.</p>
|
||||
<div class="three">
|
||||
<div class="tile">
|
||||
<div class="k">01</div>
|
||||
<div class="t">Runs a node</div>
|
||||
<div class="s">Syncs the chain from the seed node, usually under a minute.</div>
|
||||
<div class="t">Runs in the background</div>
|
||||
<div class="s">The window can close. The miner keeps going from the menu bar or the tray.</div>
|
||||
</div>
|
||||
<div class="tile">
|
||||
<div class="k">02</div>
|
||||
<div class="t">Mines on your GPU</div>
|
||||
<div class="s">A new program every hour. The next one compiles while you mine.</div>
|
||||
<div class="t">Uses your graphics card</div>
|
||||
<div class="s">Each card is tuned for hashes per watt by itself. You can tune again any time.</div>
|
||||
</div>
|
||||
<div class="tile">
|
||||
<div class="k">03</div>
|
||||
<div class="t">Pays your address</div>
|
||||
<div class="s">Block rewards go to an EVM address you choose or one made here.</div>
|
||||
<div class="t">Pays an address you own</div>
|
||||
<div class="s">Every block this machine finds pays one address: one made here, or one you paste.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cta">
|
||||
<button class="btn primary big" id="btn-begin">Get started</button>
|
||||
</div>
|
||||
<p class="seedline mono">Nobody from Igneum will ever ask for your seed.</p>
|
||||
<p class="seedline mono">Nobody from Igneum will ever ask for your key.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
|
@ -96,20 +102,20 @@
|
|||
<section class="screen" id="screen-cards">
|
||||
<div class="step">
|
||||
<div class="eyebrow">step 1 of 2</div>
|
||||
<h2>Your GPU</h2>
|
||||
<h2>Your graphics card</h2>
|
||||
<p class="sub" id="cards-sub">Asking the graphics cards to report in.</p>
|
||||
<div class="cards" id="cards-list">
|
||||
<div class="card detecting" id="cards-detecting">
|
||||
<div class="spinner"></div>
|
||||
<div>
|
||||
<div class="t">Detecting</div>
|
||||
<div class="s mono" id="detect-text">loading the Metal worker</div>
|
||||
<div class="s mono" id="detect-text">asking the graphics cards to report in</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="note" id="cards-note" hidden></p>
|
||||
<p class="note" id="cards-power" hidden>Igneum caps each NVIDIA card's power at 80% of its default limit to keep it stable. This needs administrator rights once, when mining starts; the limit goes back to what it was on quit. Settings has a slider per card.</p>
|
||||
<p class="note" id="cards-help" hidden>Each card you switch on gets its own worker. An integrated GPU is off by default: it is slow and shares the machine's memory.</p>
|
||||
<p class="note" id="cards-power" hidden>NVIDIA cards start at 80% of their power limit, which keeps them stable. Windows asks for administrator rights once for that. The card row has a slider.</p>
|
||||
<p class="note" id="cards-help" hidden>An integrated GPU is off by default: it is slow and shares the machine's memory.</p>
|
||||
<div class="cta">
|
||||
<button class="btn primary" id="btn-cards-next" disabled>Continue</button>
|
||||
<button class="btn ghost" id="btn-cards-retry" hidden>Detect again</button>
|
||||
|
|
@ -122,14 +128,14 @@
|
|||
<div class="step">
|
||||
<div class="eyebrow">step 2 of 2</div>
|
||||
<h2>Where should rewards go?</h2>
|
||||
<p class="sub">Every block this machine finds pays one EVM address. Pick one way. One block in 100 pays the miner software's dev fee; Settings turns it off.</p>
|
||||
<p class="sub">Every block this machine finds pays one address. Pick one way.</p>
|
||||
<div class="options">
|
||||
<label class="option on" id="opt-generate">
|
||||
<input type="radio" name="mode" value="generate" checked>
|
||||
<span class="radio"></span>
|
||||
<span class="body">
|
||||
<span class="t">Make me an address <span class="tag">recommended</span></span>
|
||||
<span class="s">A secp256k1 key is made on this machine and stored in the app folder with the file locked to your user. You see the key once, to save it.</span>
|
||||
<span class="s">A key is made on this machine and stored in the app folder, readable by your user only. You see it once, to save it.</span>
|
||||
</span>
|
||||
</label>
|
||||
<label class="option" id="opt-paste">
|
||||
|
|
@ -137,98 +143,174 @@
|
|||
<span class="radio"></span>
|
||||
<span class="body">
|
||||
<span class="t">Use my own address</span>
|
||||
<span class="s">Paste an EVM address you control. 0x followed by 40 hex characters.</span>
|
||||
<span class="s">Paste an Ethereum-style address you control: 0x and 40 characters.</span>
|
||||
<input type="text" class="addr-input mono" id="addr-input" placeholder="0x" spellcheck="false" autocomplete="off">
|
||||
<span class="err" id="addr-err" hidden>That is not an EVM address. 0x followed by 40 hex characters.</span>
|
||||
<span class="err" id="addr-err" hidden>That is not an address. 0x followed by 40 hex characters.</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<p class="err" id="start-blocked" hidden></p>
|
||||
<div class="cta">
|
||||
<button class="btn primary big" id="btn-start">Start mining</button>
|
||||
<button class="btn primary big" id="btn-start">Continue</button>
|
||||
<button class="btn ghost" id="btn-address-back">Back</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 4. the dashboard: six pages behind the rail -->
|
||||
<!-- 4. the dashboard: four pages behind the rail -->
|
||||
<section class="screen" id="screen-dashboard">
|
||||
|
||||
<!-- Mine -->
|
||||
<section class="page" id="page-mine" data-page="mine">
|
||||
<div class="clock-card" id="m-clock" hidden>
|
||||
<p class="clock-msg" id="m-clock-msg"></p>
|
||||
<div class="row"><button class="btn small primary" id="m-clock-sync">Sync clock</button><span class="note mono small" id="m-clock-result"></span></div>
|
||||
<p class="note" id="m-clock-hint"></p>
|
||||
</div>
|
||||
|
||||
<div class="hero-row">
|
||||
<button class="toggle-big" id="btn-toggle" disabled>
|
||||
<span class="ring"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3v9"/><path d="M6.5 6.5a8 8 0 1 0 11 0"/></svg></span>
|
||||
<span class="tl" id="btn-toggle-text">Start mining</span>
|
||||
<span class="ts" id="btn-toggle-sub">waiting for the engine</span>
|
||||
</button>
|
||||
<div class="cell big ember">
|
||||
<div class="k">hash rate</div>
|
||||
<div class="v"><span id="d-hash">0.0</span><span class="unit">MH/s</span></div>
|
||||
<div class="s" id="d-hash-sub">waiting for the worker</div>
|
||||
</div>
|
||||
<div class="cell big">
|
||||
<div class="k">blocks found</div>
|
||||
<div class="v" id="d-blocks">0</div>
|
||||
<div class="s" id="d-blocks-sub">accepted by the node</div>
|
||||
</div>
|
||||
<div class="cell big">
|
||||
<div class="k">next program</div>
|
||||
<div class="v" id="d-eta">--:--</div>
|
||||
<div class="s" id="d-eta-sub">waiting for the node</div>
|
||||
<div class="totals" id="totals">
|
||||
<div class="tot"><span class="v ember" id="t-hash">0</span><span class="k">MH/s</span><span class="s" id="t-hash-sub">waiting</span></div>
|
||||
<div class="tot" id="t-power-cell"><span class="v" id="t-power">0</span><span class="k">W</span><span class="s" id="t-power-sub">drawn now</span></div>
|
||||
<div class="tot" id="t-money-cell"><span class="v" id="t-money">£0.00</span><span class="k">a day</span><span class="s" id="t-money-sub">electricity</span></div>
|
||||
<div class="tot"><span class="v" id="t-blocks">0</span><span class="k">blocks</span><span class="s" id="t-blocks-sub">this run</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-head"><h3>Your GPUs</h3><div class="eyebrow" id="d-cards-eyebrow">detecting</div></div>
|
||||
<div class="card-head"><h3>Your cards</h3><div class="head-right"><span class="eyebrow" id="d-cards-eyebrow">detecting</span><button class="btn small" id="btn-tune-all" hidden>Tune all</button></div></div>
|
||||
<div class="gpu-list" id="d-cards"><div class="empty">Waiting for the engine.</div></div>
|
||||
<p class="note" id="d-cards-note" hidden></p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-head">
|
||||
<h3>Your blocks</h3>
|
||||
<div class="stats mono"><span>last 10 min <b id="d-found-10">0</b></span><span>last hour <b id="d-found-60">0</b></span><span>dev fee <b id="d-fee">0</b></span><span>chain <b id="d-chain-blocks">0</b></span></div>
|
||||
<div class="card" id="tune-card">
|
||||
<div class="card-head"><h3>Tuning</h3><span class="eyebrow" id="tune-eyebrow">Ember Tune</span></div>
|
||||
<div class="goal-row">
|
||||
<div class="seg" id="goal-seg" role="radiogroup" aria-label="Tuning goal">
|
||||
<button class="seg-b" data-goal="efficiency" role="radio" aria-checked="false">Efficiency</button>
|
||||
<button class="seg-b" data-goal="balanced" role="radio" aria-checked="false">Balanced</button>
|
||||
<button class="seg-b" data-goal="rate" role="radio" aria-checked="false">Maximum</button>
|
||||
</div>
|
||||
<span class="goal-line" id="goal-line"></span>
|
||||
</div>
|
||||
<p class="line-text" id="tune-schedule"></p>
|
||||
<label class="switch" id="m-power-row" hidden><input type="checkbox" id="m-power-control"><span class="track"></span><span class="sw-text"><b>Power control</b> <span class="dim">Lets the app set NVIDIA power and clock limits. Windows asks for administrator rights once.</span></span></label>
|
||||
</div>
|
||||
|
||||
<div class="card node-card" id="node-card">
|
||||
<button class="disclose" id="node-toggle" aria-expanded="false" aria-controls="node-details">
|
||||
<span class="node-line"><i class="dot" id="node-dot"></i><span id="node-line-text">Node starting</span></span>
|
||||
<span class="disclose-right"><span class="dim" id="node-sub"></span><span class="chev" aria-hidden="true"></span></span>
|
||||
</button>
|
||||
<div class="details" id="node-details" hidden>
|
||||
<div class="kv">
|
||||
<div><span class="k">state</span><span class="v mono" id="n-state-v"></span><span class="m" id="n-state-m"></span></div>
|
||||
<div><span class="k">height</span><span class="v mono" id="n-blocks">0</span><span class="m" id="n-blocks-sub">blocks this node holds</span></div>
|
||||
<div><span class="k">peers</span><span class="v mono" id="n-peers">0</span><span class="m" id="n-peers-sub">other nodes it talks to</span></div>
|
||||
<div><span class="k">version</span><span class="v mono" id="n-version">--</span><span class="m" id="d-node-net">devnet v4</span></div>
|
||||
<div><span class="k">headers</span><span class="v mono" id="n-headers">0</span><span class="m">headers arrive before blocks</span></div>
|
||||
<div><span class="k">DAA score</span><span class="v mono" id="n-daa">0</span><span class="m">blocks the whole network has made</span></div>
|
||||
<div><span class="k">difficulty</span><span class="v mono" id="n-diff">0</span><span class="m">how hard the next block is to find</span></div>
|
||||
<div><span class="k">tips</span><span class="v mono" id="n-tips">0</span><span class="m">open ends of the block DAG right now</span></div>
|
||||
<div><span class="k">blue score</span><span class="v mono" id="n-blue">0</span><span class="m">blocks on the agreed main chain</span></div>
|
||||
<div><span class="k">next program</span><span class="v mono" id="d-eta">--:--</span><span class="m" id="d-eta-sub">the hourly mining program</span></div>
|
||||
<div><span class="k">last lock</span><span class="v mono" id="f-lock">none yet</span><span class="m" id="f-note">a point the miners agreed can never be undone</span></div>
|
||||
<div><span class="k">votes sent</span><span class="v mono" id="f-votes">0</span><span class="m">this machine signs a checkpoint every 30 s</span></div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="k">rules digest</div>
|
||||
<div class="box mono"><span id="n-digest">not printed yet</span><button class="btn tiny" data-copy="n-digest">Copy</button></div>
|
||||
<p class="help">Every node on the network shows the same fingerprint of the rules. A peer with another one is refused.</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="k">next rule switch</div>
|
||||
<div class="line-text" id="n-switch">none planned</div>
|
||||
<p class="help" id="n-switch-help"></p>
|
||||
<div class="switch-list mono" id="n-switches"></div>
|
||||
</div>
|
||||
</div>
|
||||
<canvas id="dag" aria-hidden="true"></canvas>
|
||||
<div class="legend"><span><i class="sw ember"></i>block this machine found</span><span><i class="sw glow"></i>just accepted</span><span><i class="sw line"></i>one minute</span></div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-head"><h3>Activity</h3><div class="eyebrow">newest first</div></div>
|
||||
<div class="card-head"><h3>Activity</h3><div class="head-right"><span class="stats mono" id="d-stats"></span><button class="btn small ghost" id="btn-open-log">Open the log</button></div></div>
|
||||
<canvas id="dag" aria-hidden="true"></canvas>
|
||||
<div class="feed" id="d-events"><div class="empty">No events yet.</div></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Earnings -->
|
||||
<section class="page" id="page-earnings" data-page="earnings" hidden>
|
||||
<div class="card">
|
||||
<div class="money">
|
||||
<div class="money-row"><span class="v" id="e-earned">£0.00</span><span class="s" id="e-earned-sub">earned: nothing is bought or sold on devnet</span></div>
|
||||
<div class="money-row"><span class="v small" id="e-ign">0.0000 IGN</span><span class="s" id="e-ign-sub">from 0 proofs</span></div>
|
||||
<div class="money-row"><span class="v small" id="e-blocks">0 blocks</span><span class="s" id="e-blocks-sub">lifetime</span></div>
|
||||
<div class="money-row"><span class="v small" id="e-cost">£0.00 a day</span><span class="s" id="e-cost-sub">electricity</span><button class="btn tiny ghost" id="e-price-btn">Set the price</button></div>
|
||||
</div>
|
||||
<label class="switch"><input type="checkbox" id="s-devfee"><span class="track"></span><span class="sw-text" id="s-devfee-text">Dev fee: 1 block in 100 pays the miner software's author</span></label>
|
||||
<p class="help" id="r-devfee-line"></p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-head"><h3>Rewards address</h3><div class="eyebrow" id="r-source"></div></div>
|
||||
<div class="addr-big mono"><span id="r-address">not set</span><button class="btn small" data-copy="r-address">Copy</button></div>
|
||||
<p class="help" id="r-address-help">Every block this machine finds pays this address.</p>
|
||||
|
||||
<button class="disclose" id="addr-toggle" aria-expanded="false" aria-controls="addr-details"><span>Change address</span><span class="chev" aria-hidden="true"></span></button>
|
||||
<div class="details" id="addr-details" hidden>
|
||||
<div class="row">
|
||||
<input type="text" class="addr-input mono" id="s-address-input" placeholder="0x" spellcheck="false" autocomplete="off" aria-label="New rewards address">
|
||||
<button class="btn small" id="s-address-save">Change</button>
|
||||
</div>
|
||||
<div class="err" id="s-address-err" hidden></div>
|
||||
<div class="ask inline" id="ask-address" hidden>
|
||||
<span class="ask-text" id="ask-address-text"></span>
|
||||
<button class="btn small primary" id="ask-address-yes">Confirm</button>
|
||||
<button class="btn small ghost" id="ask-address-no">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="disclose" id="key-toggle" aria-expanded="false" aria-controls="key-details"><span id="key-toggle-text">Show my key</span><span class="chev" aria-hidden="true"></span></button>
|
||||
<div class="details" id="key-details" hidden>
|
||||
<p class="help" id="r-key-help"></p>
|
||||
<div class="ask inline" id="ask-key">
|
||||
<span class="ask-text">Show the private key on screen? Anyone who sees it can spend what the address holds.</span>
|
||||
<button class="btn small primary" id="s-reveal">Show</button>
|
||||
<button class="btn small ghost" id="ask-key-no">Cancel</button>
|
||||
</div>
|
||||
<div class="box mono key" id="s-key-box" hidden><span id="s-key"></span><button class="btn tiny" data-copy="s-key">Copy</button><button class="btn tiny ghost" id="s-hide">Hide</button></div>
|
||||
<p class="note mono small" id="r-key-file"></p>
|
||||
</div>
|
||||
|
||||
<div class="row wrap top-gap"><button class="btn small" id="r-wallet">Open the wallet</button><span class="note">Your balance is in the wallet.</span></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Prove -->
|
||||
<section class="page" id="page-prove" data-page="prove" hidden>
|
||||
<div class="card lead-card">
|
||||
<div class="card">
|
||||
<div class="lead-row">
|
||||
<div class="lead-text">
|
||||
<h3>Prove shards on this machine</h3>
|
||||
<p class="help">Every block on Igneum is turned into a short mathematical proof, in pieces called shards. The chain assigns shards to your keys; this machine proves them and is paid for each one. On by default on an NVIDIA card with 24 GB or more (a full shard needs 20.4 GB of GPU memory, measured); off on a Mac, whose CPU prover is slow.</p>
|
||||
<h3>Prove on this machine</h3>
|
||||
<p class="help">Every block is turned into a short proof. The chain hands pieces to your cards; each piece proven pays IGN.</p>
|
||||
</div>
|
||||
<label class="switch lg" title="Prove assigned shards"><input type="checkbox" id="s-prove" aria-label="Prove shards on this machine"><span class="track"></span></label>
|
||||
<label class="switch lg" title="Prove on this machine"><input type="checkbox" id="s-prove" aria-label="Prove on this machine"><span class="track"></span></label>
|
||||
</div>
|
||||
<p class="note" id="pv-note">Off. Switch it on and this machine proves the shards the chain assigns to its keys.</p>
|
||||
<ul class="tier-list" id="pv-tiers"></ul>
|
||||
<p class="line-text" id="pv-line"></p>
|
||||
<p class="note" id="pv-note"></p>
|
||||
<div class="row" id="pv-setup-row" hidden><button class="btn small primary" id="pv-setup">Set up</button><span class="note">About 20 minutes, once.</span></div>
|
||||
<button class="disclose" id="pv-toggle" aria-expanded="false" aria-controls="pv-details"><span>Details</span><span class="chev" aria-hidden="true"></span></button>
|
||||
<div class="details" id="pv-details" hidden>
|
||||
<div class="kv">
|
||||
<div><span class="k">verifier</span><span class="v" id="pv-verifier">not read yet</span><span class="m" id="pv-verifier-help">the node checks a proof before it counts</span></div>
|
||||
<div><span class="k">segments</span><span class="v" id="pv-seg">none yet</span><span class="m" id="pv-seg-note">whole segments this machine proved</span></div>
|
||||
</div>
|
||||
<div class="strip four">
|
||||
<div class="cell"><div class="k">state</div><div class="v state" id="pv-state">off</div><div class="s" id="pv-state-sub">not proving</div></div>
|
||||
<div class="cell"><div class="k">assigned</div><div class="v" id="pv-assigned">0</div><div class="s">shards given to your keys</div></div>
|
||||
<div class="cell"><div class="k">proven</div><div class="v" id="pv-submitted">0</div><div class="s">proofs sent to the node</div></div>
|
||||
<div class="cell"><div class="k">paid</div><div class="v" id="pv-paid">0</div><div class="s" id="pv-paid-sub">shards paid out</div></div>
|
||||
</div>
|
||||
<p class="note" id="pv-seg-note" hidden></p>
|
||||
<div class="grid2">
|
||||
<div class="card">
|
||||
<div class="card-head"><h3>Verifier</h3><div class="eyebrow">the node's check</div></div>
|
||||
<div class="big-word" id="pv-verifier">not read yet</div>
|
||||
<p class="help" id="pv-verifier-help">Before a proof counts, the node checks it. A node without a verifier passes proofs along and never includes them.</p>
|
||||
<p class="note" id="pv-verifier-note" hidden></p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-head"><h3>Program</h3><div class="eyebrow">pinned guest</div></div>
|
||||
<div class="field">
|
||||
<div class="k">shard program id</div>
|
||||
<div class="box mono"><span id="pv-program">not read yet</span><button class="btn tiny" data-copy="pv-program">Copy</button></div>
|
||||
|
|
@ -236,105 +318,63 @@
|
|||
<div class="field">
|
||||
<div class="k">aggregator id</div>
|
||||
<div class="box mono"><span id="pv-aggregator">not read yet</span><button class="btn tiny" data-copy="pv-aggregator">Copy</button></div>
|
||||
</div>
|
||||
<p class="help">Every proof names the program that made it. Other nodes accept a proof only from these two ids.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Rewards -->
|
||||
<section class="page" id="page-rewards" data-page="rewards" hidden>
|
||||
<!-- Settings -->
|
||||
<section class="page" id="page-settings" data-page="settings" hidden>
|
||||
<div class="card">
|
||||
<div class="card-head"><h3>Rewards address</h3><div class="eyebrow" id="r-source"></div></div>
|
||||
<div class="addr-big mono"><span id="r-address">not set</span><button class="btn small" data-copy="r-address">Copy</button></div>
|
||||
<p class="help" id="r-address-help">Every block this machine finds pays this address.</p>
|
||||
<div class="card-head"><h3>Tuning</h3><span class="eyebrow" id="s-tune-eyebrow">Ember Tune</span></div>
|
||||
<div class="goal-row">
|
||||
<div class="seg" id="goal-seg-2" role="radiogroup" aria-label="Tuning goal">
|
||||
<button class="seg-b" data-goal="efficiency" role="radio" aria-checked="false">Efficiency</button>
|
||||
<button class="seg-b" data-goal="balanced" role="radio" aria-checked="false">Balanced</button>
|
||||
<button class="seg-b" data-goal="rate" role="radio" aria-checked="false">Maximum</button>
|
||||
</div>
|
||||
<div class="strip three">
|
||||
<div class="cell"><div class="k">blocks found</div><div class="v" id="r-blocks">0</div><div class="s">lifetime, accepted by the node</div></div>
|
||||
<div class="cell"><div class="k">this run</div><div class="v" id="r-session">0</div><div class="s" id="r-session-sub">since the app started</div></div>
|
||||
<div class="cell"><div class="k">balance</div><div class="v dim" id="r-balance">--</div><div class="s">shown in the wallet, not here yet</div></div>
|
||||
<span class="goal-line" id="goal-line-2"></span>
|
||||
</div>
|
||||
<div class="grid2">
|
||||
<div class="card" id="r-key-card">
|
||||
<div class="card-head"><h3>Save your key</h3><div class="eyebrow ember">once</div></div>
|
||||
<p class="help" id="r-key-help">The key for this address was made on this machine and is stored in the app folder, readable by your user only. Keep a copy somewhere safe: anyone with the key can spend what the address holds.</p>
|
||||
<div class="row" id="r-key-row"><button class="btn small" id="s-reveal">Show my key</button><button class="btn small ghost" id="s-hide" hidden>Hide</button></div>
|
||||
<div class="box mono key" id="s-key-box" hidden><span id="s-key"></span><button class="btn tiny" data-copy="s-key">Copy</button></div>
|
||||
<p class="note mono small" id="r-key-file"></p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-head"><h3>Use another address</h3></div>
|
||||
<p class="help">Paste an EVM address you control. The miner restarts and pays the new address from the next block.</p>
|
||||
<label class="switch"><input type="checkbox" id="s-sweep"><span class="track"></span><span class="sw-text"><b>Ember Tune</b> <span class="dim">Tunes every card for hashes per watt: once after install, then every 7 days, and after a driver or program change.</span></span></label>
|
||||
<label class="switch"><input type="checkbox" id="s-power-control"><span class="track"></span><span class="sw-text"><b>Power control</b> <span class="dim">Lets the app set NVIDIA power and clock limits. Windows asks for administrator rights once. Off, NVIDIA cards are measured only.</span><span class="dim" id="s-power-note"></span></span></label>
|
||||
<label class="switch" id="s-climb-row" hidden><input type="checkbox" id="s-climb"><span class="track"></span><span class="sw-text"><b>Hill climb</b> <span class="dim">Searches around the best point instead of walking the ladders.</span></span></label>
|
||||
<div class="field">
|
||||
<div class="k">electricity price</div>
|
||||
<div class="row">
|
||||
<input type="text" class="addr-input mono" id="s-address-input" placeholder="0x" spellcheck="false" autocomplete="off" aria-label="New rewards address">
|
||||
<button class="btn small" id="s-address-save">Change</button>
|
||||
</div>
|
||||
<div class="err" id="s-address-err" hidden></div>
|
||||
<p class="note" id="r-devfee-line"></p>
|
||||
<div class="row"><button class="btn small ghost" id="r-wallet">Open the wallet page</button></div>
|
||||
<input type="number" class="addr-input mono short" id="s-price" min="0" max="200" step="0.1" placeholder="28" aria-label="Electricity price in pence per kWh"><span class="note">pence per kWh. Every £ figure uses it.</span>
|
||||
<button class="btn small" id="s-price-save">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<p class="line-text" id="s-tune-schedule"></p>
|
||||
</div>
|
||||
|
||||
<!-- Node -->
|
||||
<section class="page" id="page-node" data-page="node" hidden>
|
||||
<div class="strip four">
|
||||
<div class="cell" id="n-state-cell"><div class="k">node</div><div class="v state" id="d-node">starting</div><div class="s" id="d-node-sub">opening the database</div></div>
|
||||
<div class="cell"><div class="k">height</div><div class="v" id="n-blocks">0</div><div class="s" id="n-blocks-sub">blocks this node holds</div></div>
|
||||
<div class="cell"><div class="k">peers</div><div class="v" id="n-peers">0</div><div class="s" id="n-peers-sub">other nodes it talks to</div></div>
|
||||
<div class="cell"><div class="k">version</div><div class="v small" id="n-version">--</div><div class="s" id="d-node-net">devnet v4</div></div>
|
||||
</div>
|
||||
<div class="clock-card" id="n-clock" hidden>
|
||||
<p class="clock-msg" id="n-clock-msg"></p>
|
||||
<div class="row"><button class="btn small primary" id="n-clock-sync">Sync clock</button><span class="note mono small" id="n-clock-result"></span></div>
|
||||
<p class="note" id="n-clock-hint"></p>
|
||||
</div>
|
||||
<div class="grid2">
|
||||
<div class="card">
|
||||
<div class="card-head"><h3>Chain</h3><div class="eyebrow" id="n-reading">reading</div></div>
|
||||
<div class="kv">
|
||||
<div><span class="k">headers</span><span class="v mono" id="n-headers">0</span></div>
|
||||
<div><span class="k">daa score</span><span class="v mono" id="n-daa">0</span></div>
|
||||
<div><span class="k">difficulty</span><span class="v mono" id="n-diff">0</span></div>
|
||||
<div><span class="k">tips</span><span class="v mono" id="n-tips">0</span></div>
|
||||
<div><span class="k">blue score</span><span class="v mono" id="n-blue">0</span></div>
|
||||
</div>
|
||||
<p class="help">Headers arrive before blocks. The DAA score counts blocks the whole network made; the difficulty is how hard the next one is to find.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-head"><h3>Consensus</h3><div class="eyebrow">the rules</div></div>
|
||||
<div class="card-head"><h3>This machine</h3></div>
|
||||
<label class="switch"><input type="checkbox" id="s-login"><span class="track"></span><span class="sw-text"><b>Start at login</b> <span class="dim">Opens when you sign in and keeps mining in the background.</span></span></label>
|
||||
<label class="switch"><input type="checkbox" id="s-jobs-allow"><span class="track"></span><span class="sw-text"><b>Allow remote jobs from Igneum</b> <span class="dim">Signed jobs (a benchmark, a script, logs to collect) run here once and report back.</span></span></label>
|
||||
<div class="row wrap indent"><span class="note" id="s-jobs-note"></span><button class="btn tiny ghost" id="s-jobs-check">Check now</button><button class="btn tiny ghost" id="s-jobs-history-btn" aria-expanded="false">History</button></div>
|
||||
<div class="details indent" id="s-jobs-history" hidden></div>
|
||||
<p class="note mono small indent" id="s-jobs-key" hidden></p>
|
||||
<div class="field">
|
||||
<div class="k">digest</div>
|
||||
<div class="box mono"><span id="n-digest">not printed yet</span><button class="btn tiny" data-copy="n-digest">Copy</button></div>
|
||||
<p class="help">The fingerprint of the rules this node runs. Every node on the network shows the same one; a peer with another is refused.</p>
|
||||
<div class="k">name</div>
|
||||
<div class="row">
|
||||
<input type="text" class="addr-input" id="s-name" placeholder="a name for this machine" maxlength="40" spellcheck="false" aria-label="Machine name">
|
||||
<button class="btn small" id="s-name-save">Rename</button>
|
||||
</div>
|
||||
<p class="help">A label for you only.</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="k">next switch</div>
|
||||
<div class="big-word" id="n-switch">none planned</div>
|
||||
<p class="help" id="n-switch-help">A switch is a planned rule change. The node applies it by itself when the chain reaches that height.</p>
|
||||
<div class="switch-list mono" id="n-switches"></div>
|
||||
<div class="k">appearance</div>
|
||||
<div class="seg" id="theme-seg" role="radiogroup" aria-label="Appearance">
|
||||
<button class="seg-b" data-theme="system" role="radio" aria-checked="true">System</button>
|
||||
<button class="seg-b" data-theme="light" role="radio" aria-checked="false">Light</button>
|
||||
<button class="seg-b" data-theme="dark" role="radio" aria-checked="false">Dark</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-head"><h3>Finality</h3><div class="eyebrow">miner-only</div></div>
|
||||
<div class="kv">
|
||||
<div><span class="k">last lock</span><span class="v mono" id="f-lock">none yet</span></div>
|
||||
<div><span class="k">age</span><span class="v mono" id="f-age">n/a</span></div>
|
||||
<div><span class="k">votes sent</span><span class="v mono" id="f-votes">0</span></div>
|
||||
</div>
|
||||
<p class="help" id="f-note">A lock is a point the miners have agreed can never be undone. This machine votes on one every 30 s.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-head"><h3>Sync</h3><div class="eyebrow" id="n-sync-eyebrow"></div></div>
|
||||
<div class="big-word" id="n-sync-word">starting</div>
|
||||
<p class="help" id="n-note"></p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Updates -->
|
||||
<section class="page" id="page-updates" data-page="updates" hidden>
|
||||
<div class="card lead-card">
|
||||
<div class="card">
|
||||
<div class="lead-row">
|
||||
<div class="lead-text">
|
||||
<h3 id="s-version">Igneum Miner</h3>
|
||||
|
|
@ -342,79 +382,39 @@
|
|||
</div>
|
||||
<div class="row">
|
||||
<button class="btn small primary" id="s-install" hidden>Install now</button>
|
||||
<button class="btn small" id="s-update">Check now</button>
|
||||
<button class="btn small" id="s-update">Check</button>
|
||||
</div>
|
||||
</div>
|
||||
<label class="switch"><input type="checkbox" id="s-auto-update"><span class="track"></span><span>Install updates by itself</span></label>
|
||||
<p class="help">Downloads in the background and installs at a quiet moment, never mid-program. Off: it downloads, then waits for Install now.</p>
|
||||
<label class="switch"><input type="checkbox" id="s-auto-update"><span class="track"></span><span class="sw-text"><b>Install updates by itself</b> <span class="dim">Downloads in the background and installs at a quiet moment, never mid-program.</span></span></label>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="lead-row">
|
||||
<div class="lead-text">
|
||||
<h3>Remote jobs</h3>
|
||||
<p class="help">Igneum publishes signed jobs (a benchmark, a script, logs to collect) next to the update manifest. This machine runs each one once and reports back. Only jobs signed by Igneum's key run.</p>
|
||||
</div>
|
||||
<button class="btn small" id="s-jobs-check">Check now</button>
|
||||
</div>
|
||||
<p class="note" id="s-jobs-note"></p>
|
||||
<div class="job-history" id="s-jobs-history"></div>
|
||||
<p class="note mono small" id="s-jobs-key"></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Settings -->
|
||||
<section class="page" id="page-settings" data-page="settings" hidden>
|
||||
<div class="card">
|
||||
<div class="card-head"><h3>Graphics cards</h3><div class="eyebrow" id="s-cards-eyebrow"></div></div>
|
||||
<div class="set-cards" id="s-cards"><div class="empty">No card yet.</div></div>
|
||||
<label class="switch"><input type="checkbox" id="s-sweep"><span class="track"></span><span>Ember Tune: tune every card for hashes per watt</span></label>
|
||||
<p class="help">Once after install, then weekly and after a driver or program change: the power limit steps from 100% down to 50%, then the core clock from its maximum down to 60%, 75 s a step on the live program; the memory clock is never touched. The card keeps the point with the most hashes per watt within 1% of its top rate. A step with a rejected hash, a hot GPU or a dragged memory clock is reverted. A card whose model the fleet already knows starts at that point and confirms it in two steps. Every result goes back to the fleet without anything that identifies you. A cap you set by hand is left alone.</p>
|
||||
<label class="switch"><input type="checkbox" id="s-power-control"><span class="track"></span><span>Power control: let the app set NVIDIA limits</span></label>
|
||||
<p class="help">Windows asks for administrator rights once; the NVIDIA cap and the tune need them. Off, the app never asks and NVIDIA cards measure only. AMD cards need no rights. <span id="s-power-note"></span></p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-head"><h3>This machine</h3></div>
|
||||
<label class="switch"><input type="checkbox" id="s-login"><span class="track"></span><span>Start at login</span></label>
|
||||
<p class="help">The miner opens when you sign in and keeps mining in the background.</p>
|
||||
<label class="switch"><input type="checkbox" id="s-jobs-allow"><span class="track"></span><span>Allow remote jobs from Igneum</span></label>
|
||||
<p class="help">Signed jobs from Igneum run on this machine and report back. Updates shows what ran.</p>
|
||||
<label class="switch"><input type="checkbox" id="s-vote"><span class="track"></span><span>Vote on finality checkpoints</span></label>
|
||||
<p class="help">Your miner signs a checkpoint every 30 s. Votes are what lock the chain; leave it on.</p>
|
||||
<div class="field">
|
||||
<div class="k">name</div>
|
||||
<div class="row">
|
||||
<input type="text" class="addr-input" id="s-name" placeholder="a name for this machine" maxlength="40" spellcheck="false" aria-label="Machine name">
|
||||
<button class="btn small" id="s-name-save">Rename</button>
|
||||
</div>
|
||||
<p class="help">A label for you only. Keys come from the machine id <span class="mono" id="s-mid"></span>, never from the name.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-head"><h3>Dev fee</h3></div>
|
||||
<label class="switch"><input type="checkbox" id="s-devfee"><span class="track"></span><span id="s-devfee-text">Dev fee 1% (1 block in 100)</span></label>
|
||||
<p class="help" id="s-devfee-note">One block in 100 is mined for the miner software's author, the same way every GPU miner takes a fee. The protocol itself takes nothing. This switch turns it off.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-head"><h3>Logs</h3></div>
|
||||
<div class="row wrap">
|
||||
<button class="btn small" id="s-log-copy">Copy the log</button>
|
||||
<button class="btn small ghost" id="s-log-open">Show the log</button>
|
||||
<span class="note">The last lines the node and the miner wrote, for a support message.</span>
|
||||
</div>
|
||||
<p class="help">Copies the last lines the node and the miner wrote, for a support message. Show the log opens the drawer with every line.</p>
|
||||
<p class="note mono small" id="s-log-dir"></p>
|
||||
<p class="note mono small" id="s-node-dir"></p>
|
||||
</div>
|
||||
|
||||
<details class="card adv" id="s-advanced">
|
||||
<summary><h3>Advanced</h3><span class="eyebrow">devnet tools</span></summary>
|
||||
<label class="switch"><input type="checkbox" id="s-trust"><span class="track"></span><span>Trust proof records without verifying them</span></label>
|
||||
<p class="help" id="s-trust-note">Devnet only. When no verifier is found next to the engine, the node includes proof records it never checked. A found verifier always wins. Changing this restarts the node.</p>
|
||||
<div class="row"><button class="btn small ghost" id="s-live" hidden>Open the live devnet page</button></div>
|
||||
<summary><h3>Advanced</h3><span class="eyebrow">devnet</span></summary>
|
||||
<label class="switch"><input type="checkbox" id="s-vote"><span class="track"></span><span class="sw-text"><b>Vote on finality checkpoints</b> <span class="dim">Signs a checkpoint every 30 s. Votes lock the chain; leave it on.</span></span></label>
|
||||
<label class="switch"><input type="checkbox" id="s-trust"><span class="track"></span><span class="sw-text"><b>Trust proof records without verifying them</b> <span class="dim">Devnet only. Without a verifier the node includes records it never checked. Changing this restarts the node.</span></span></label>
|
||||
<div class="ask inline" id="ask-trust" hidden>
|
||||
<span class="ask-text" id="ask-trust-text"></span>
|
||||
<button class="btn small primary" id="ask-trust-yes">Confirm</button>
|
||||
<button class="btn small ghost" id="ask-trust-no">Cancel</button>
|
||||
</div>
|
||||
<div class="row wrap"><button class="btn small ghost" id="s-live" hidden>Open the live devnet page</button><span class="note mono small" id="s-mid"></span></div>
|
||||
</details>
|
||||
</section>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!-- the update card (app.js, UpdateCard): one update, centred; Later, Escape or the backdrop leaves the strip above -->
|
||||
<!-- the update card (app.js, UpdateCard) -->
|
||||
<div class="upd-wrap" id="upd" hidden>
|
||||
<div class="upd-card" id="upd-card" role="dialog" aria-modal="true" aria-labelledby="upd-name" aria-describedby="upd-line" tabindex="-1">
|
||||
<div class="upd-mark" id="upd-mark">
|
||||
|
|
@ -440,16 +440,15 @@
|
|||
<div class="eyebrow ember">shown once</div>
|
||||
<h2>Save your key</h2>
|
||||
<p class="sub">This key controls the rewards address. Keep a copy somewhere safe. Anyone with the key can spend what the address holds.</p>
|
||||
<div class="field">
|
||||
<div class="k">rewards address</div>
|
||||
<div class="box mono"><span id="key-address"></span><button class="btn tiny" data-copy="key-address">Copy</button></div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="k">private key</div>
|
||||
<div class="box mono key"><span id="key-private"></span><button class="btn tiny" data-copy="key-private">Copy</button></div>
|
||||
</div>
|
||||
<p class="note">Stored at <span class="mono" id="key-file"></span>, readable by your user only. Rewards can show it again.</p>
|
||||
<p class="note">On devnet the vote keys are test keys derived from the miner's label. Mainnet vote keys will be random and stored like this wallet.</p>
|
||||
<div class="field">
|
||||
<div class="k">rewards address</div>
|
||||
<div class="box mono"><span id="key-address"></span><button class="btn tiny" data-copy="key-address">Copy</button></div>
|
||||
</div>
|
||||
<p class="note">Stored at <span class="mono" id="key-file"></span>, readable by your user only. Earnings can show it again.</p>
|
||||
<label class="check"><input type="checkbox" id="key-ack"><span>I have saved my key</span></label>
|
||||
<div class="cta">
|
||||
<button class="btn primary big" id="btn-key-done" disabled>Start mining</button>
|
||||
|
|
@ -457,8 +456,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- the log drawer: chips filter by source, search filters by text, the ruler and the jump controls move in time.
|
||||
The list is virtualised (fixed row height, only the visible rows are in the DOM) so 20,000 lines scroll smoothly. -->
|
||||
<!-- the log drawer (unchanged from miner-ui-2) -->
|
||||
<div class="drawer" id="drawer" aria-label="Logs">
|
||||
<div class="drawer-grip" id="drawer-grip" title="Drag to resize"><i></i></div>
|
||||
<div class="drawer-head">
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ const src = readFileSync(join(dirname(fileURLToPath(import.meta.url)), 'app.js')
|
|||
const mod = { exports: {} };
|
||||
new Function('module', src)(mod);
|
||||
const { model, point } = mod.exports.TuneLine;
|
||||
const View = mod.exports.View;
|
||||
const tuneNotice = mod.exports.tuneNotice;
|
||||
const NOW = 1_800_000_000;
|
||||
const card = over => ({ vendor: 'nvidia', sweep_state: 'idle', sweep_note: '', sweep_pct: 0, power_pct: 80, sweep_at: 0, tune_line: '', tune_source: '', tune_clock_mhz: 0, tune_control: true, pinned: false, ...over });
|
||||
|
||||
|
|
@ -42,3 +44,39 @@ test('running, stopped, idle and off', () => {
|
|||
assert.equal(model(card({ vendor: 'other' }), NOW).kind, 'off');
|
||||
assert.equal(point({ tune_clock_mhz: 0, sweep_pct: 0, power_pct: 0 }), '');
|
||||
});
|
||||
|
||||
test('a card never shows 0 MH/s without a reason word: tuning, held and tuned rows, the button, the strip', () => {
|
||||
const base = { key: 'nvidia:0:x', name: 'RTX 5090', vendor: 'nvidia', kind: 'discrete', enabled: true, state: 'tuning', hash_now: 123.4, power_w: 219.6, tune_step: 7, tune_steps: 9, tune_eta_s: 230, tune_plan: 'full', sweep_note: 'tuning: holding 2472 MHz · 100% · 41 s (step 7 of 9)', vram_mb: 32768 };
|
||||
const r = View.cardRow(base);
|
||||
assert.equal(r.word, 'tuning: step 7 of 9 · measuring 220 W');
|
||||
assert.equal(r.hash, '123', 'the live rate stays in the rate column');
|
||||
assert.equal(r.tone, 'on');
|
||||
const h = View.cardRow({ ...base, state: 'held', hash_now: 0, power_w: 0, message: 'held for a remote job: Ember Tune on PC 1', tune_steps: 0 });
|
||||
assert.equal(h.word, 'held for a remote job: Ember Tune on PC 1');
|
||||
assert.equal(h.hash, '', 'blank, never 0');
|
||||
const off = View.cardRow({ ...base, state: 'off', enabled: false, hash_now: 0 });
|
||||
assert.equal(off.word, 'off'); assert.equal(off.hash, '');
|
||||
// the big button during a tune
|
||||
const t = View.toggle({ cards: [base], state: 'mining', paused: false }, { synced: true }, {});
|
||||
assert.equal(t.label, 'Tuning'); assert.equal(t.disabled, true);
|
||||
assert.equal(t.sub, 'mining again in about 4 min · RTX 5090');
|
||||
const held = View.toggle({ cards: [{ ...base, state: 'held' }], state: 'waiting', paused: false }, { synced: true }, {});
|
||||
assert.equal(held.label, 'Held'); assert.equal(held.disabled, true);
|
||||
// the strip
|
||||
const n = tuneNotice([base]);
|
||||
assert.equal(n.kind, 'tuning');
|
||||
assert.equal(n.text, 'Tuning RTX 5090: the full tune, step 7 of 9. Mining again in about 4 min.');
|
||||
assert.equal(tuneNotice([{ ...base, state: 'mining' }]), null);
|
||||
// the finished row: TuneLine's tuned model (the table's "Tuned: ..." line with the point)
|
||||
const done = model({ ...base, state: 'mining', sweep_state: 'idle', tune_line: 'Tuned: 123.4 MH/s at 220 W (0.561 MH/W)', tune_source: 'full', tune_clock_mhz: 2470, sweep_pct: 100, sweep_at: NOW - 60, tune_control: true }, NOW);
|
||||
assert.equal(done.kind, 'tuned');
|
||||
assert.equal(done.text, 'Tuned: 123.4 MH/s at 220 W (0.561 MH/W)');
|
||||
assert.match(done.note, /^2470 MHz at 100%, full tune, 1 min ago/);
|
||||
assert.equal(View.tuneEta(30), 'under a minute');
|
||||
assert.equal(View.tuneEta(700), 'about 12 min');
|
||||
});
|
||||
|
||||
test('Ember 2: £ a day from watts and pence per kWh', () => {
|
||||
assert.equal(mod.exports.TuneLine.pounds(310, 28.5), '£2.12');
|
||||
assert.equal(mod.exports.TuneLine.pounds(102.7, 28.5), '£0.70');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ test('release notes: sentences, three lines under 70 characters, the rest behind
|
|||
|
||||
test('what the card says: available, downloading with the ring, ready, installing, failed, manual, waiting', () => {
|
||||
const a = model(upd({ status: 'available', downloaded: false, ready: false, progress: 0 }), {});
|
||||
assert.equal(a.name, 'Igneum Ember 0.3.7'); assert.equal(a.line, 'is available.'); assert.equal(a.stage, 'available');
|
||||
assert.equal(a.name, 'Igneum Miner 0.3.7'); assert.equal(a.line, 'is available.'); assert.equal(a.stage, 'available');
|
||||
assert.equal(a.key, 'update:0.3.7:pending'); assert.equal(a.ring, 'none'); assert.equal(a.size, '21 MB');
|
||||
assert.deepEqual(a.actions.map((x) => x.label), ['Install now', 'Later']); assert.equal(a.dismissable, true);
|
||||
const d = model(upd({ status: 'downloading', downloaded: false, ready: false, progress: 0.43 }), {});
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@ const V = mod.exports.View;
|
|||
|
||||
const card = (over) => ({ key: 'nvidia:0:RTX 5090', name: 'NVIDIA GeForce RTX 5090', vendor: 'nvidia', kind: 'discrete', worker: 'CUDA', vram_mb: 32768, enabled: true, state: 'mining', hash_now: 124.3, hash_avg: 120, accepted: 3, rejected: 0, identities: 8, ids: [], prepared: true, restart_in_s: 0, message: '', reason: '', power_w: 410.2, power_limit_w: 460, power_default_w: 575, power_pct: 80, power_applied: true, temp_gpu: 61, temp_mem: 72, telemetry_at: 1, ...over });
|
||||
|
||||
test('the six sections and their order', () => {
|
||||
assert.deepEqual(V.PAGES.map((p) => p.id), ['mine', 'prove', 'rewards', 'node', 'updates', 'settings']);
|
||||
assert.equal(V.page('node').title, 'Node');
|
||||
test('the four sections and their order (miner-ui-3)', () => {
|
||||
assert.deepEqual(V.PAGES.map((p) => p.id), ['mine', 'earnings', 'prove', 'settings']);
|
||||
assert.equal(V.page('earnings').title, 'Earnings');
|
||||
assert.equal(V.page('nonsense').id, 'mine');
|
||||
});
|
||||
|
||||
|
|
@ -129,9 +129,9 @@ test('the prove words follow the switch, the setup, the node and the status', ()
|
|||
assert.equal(V.verifierWords({}).word, 'not read yet');
|
||||
});
|
||||
|
||||
test('the dev-fee lines name the share and where Settings turns it off', () => {
|
||||
test('the dev-fee lines name the share and the switch that turns it off', () => {
|
||||
const s = (on, fee) => ({ settings: { dev_fee: on }, mining: { fee_total: fee }, dev_fee: { on, percent: on ? 1 : 0, address: '0x1234567890abcdef1234567890abcdef12345678', line: '' } });
|
||||
assert.equal(V.devFeeLine(s(true, 12)), 'One block in 100 pays the miner software’s dev fee (12 so far). Settings turns it off.');
|
||||
assert.equal(V.devFeeLine(s(true, 12)), 'One block in 100 pays the miner software’s dev fee (12 so far). The switch above turns it off.');
|
||||
assert.equal(V.devFeeLine(s(false, 0)), 'The dev fee is off. Every block pays this address.');
|
||||
assert.equal(V.devFeeText(s(true, 0)), 'Dev fee 1% (1 block in 100) to 0x123456…5678');
|
||||
assert.match(V.devFeeText(s(false, 0)), /^Dev fee off/);
|
||||
|
|
@ -139,7 +139,7 @@ test('the dev-fee lines name the share and where Settings turns it off', () => {
|
|||
|
||||
test('the remote-jobs line and the helpers', () => {
|
||||
const title = (j) => j.title || j.kind;
|
||||
assert.equal(V.jobsNote({ allowed: false }, 1000, title), 'Off: nothing runs here until Settings allows remote jobs.');
|
||||
assert.equal(V.jobsNote({ allowed: false }, 1000, title), 'Off: nothing runs here until the switch above allows remote jobs.');
|
||||
assert.equal(V.jobsNote({ allowed: true, url_set: false }, 1000, title), 'No jobs address in this build.');
|
||||
assert.equal(V.jobsNote({ allowed: true, url_set: true, active: true, id: 'job-3', kind: 'build', title: 'build' }, 1000, title), 'Running build (job-3).');
|
||||
assert.equal(V.jobsNote({ allowed: true, url_set: true, checked_at: 940, queued: 2 }, 1000, title), 'Nothing running; checked 1 min ago; 2 queued.');
|
||||
|
|
@ -198,3 +198,120 @@ test('hot-plug (src/hotplug.rs): a removed card and a faulty card are shown as s
|
|||
const t2 = V.toggle({ state: 'mining', paused: false, cards: [card(), gone] }, { synced: true }, {});
|
||||
assert.equal(t2.sub, 'mining on 1 of 1 card');
|
||||
});
|
||||
|
||||
// ---- miner-ui-3: the row as the hero object, the money, the tune line, the prove tier, the node line, the asks ----
|
||||
const NOW = 1_800_000_000;
|
||||
const settings = (over) => ({ settings: { sweep: true, tuning_off: false, tune_period_s: 604800, power_control: true, ...over }, now: NOW });
|
||||
|
||||
test('the row word: every state carries a reason, and a paused miner says paused on a card that is on', () => {
|
||||
const m = { paused: true, state: 'paused' };
|
||||
assert.equal(V.rowWord(V.cardRow(card({ state: 'off', hash_now: 0 })), m), 'paused');
|
||||
assert.equal(V.rowWord(V.cardRow(card({ state: 'waiting', hash_now: 0 })), m), 'paused');
|
||||
assert.equal(V.rowWord(V.cardRow(card()), { paused: false, state: 'mining' }), 'mining');
|
||||
assert.equal(V.rowWord(V.cardRow(card({ enabled: false, state: 'off' })), m), 'off');
|
||||
assert.equal(V.cardRow(card({ state: 'held', hash_now: 0, message: 'held for a remote job: shard benchmark' })).word, 'held for a remote job: shard benchmark');
|
||||
assert.equal(V.cardRow(card({ state: 'tuning', tune_step: 3, tune_steps: 9, power_w: 300 })).word, 'tuning: step 3 of 9 · measuring 300 W');
|
||||
assert.equal(V.cardRow(card({ state: 'starting', hash_now: 0 })).word, 'starting');
|
||||
assert.equal(V.cardRow(card({ state: 'failed', hash_now: 0 })).tone, 'bad');
|
||||
});
|
||||
|
||||
test('money: £ a day from watts at a price, nothing without one, the cells leave out what is not reported', () => {
|
||||
assert.equal(V.poundsPerDay(310, 28.5).toFixed(4), '2.1204');
|
||||
assert.equal(V.money(V.poundsPerDay(310, 28.5)), '£2.12');
|
||||
assert.equal(V.poundsPerDay(310, 0), null);
|
||||
assert.equal(V.poundsPerDay(0, 28), null);
|
||||
assert.equal(V.money(123.4), '£123');
|
||||
assert.equal(V.money(null), '');
|
||||
const c = V.cells(card(), 28);
|
||||
assert.equal(c.hash, '124'); assert.equal(c.power, '410 W'); assert.equal(c.money, '£2.76'); assert.equal(c.temp, '61 °C'); assert.equal(c.needsPrice, false); assert.equal(c.noReading, '');
|
||||
assert.equal(c.eff, '0.30 MH/W');
|
||||
const noPrice = V.cells(card(), 0);
|
||||
assert.equal(noPrice.money, ''); assert.equal(noPrice.needsPrice, true);
|
||||
const apple = V.cells(card({ vendor: 'apple', kind: 'apple', power_w: 0, temp_gpu: 0, temp_mem: 0, eff_mhw: 0 }), 28);
|
||||
assert.equal(apple.power, ''); assert.equal(apple.money, ''); assert.equal(apple.temp, ''); assert.equal(apple.needsPrice, false);
|
||||
assert.equal(apple.noReading, 'No power or temperature reading on Apple silicon');
|
||||
assert.equal(V.cells(card({ kind: 'integrated', vendor: 'other', power_w: 0, temp_gpu: 0, eff_mhw: 0 }), 28).noReading, 'No power or temperature reading on an integrated GPU');
|
||||
assert.equal(V.wattsTotal([card(), card({ key: 'b', power_w: 100 }), card({ key: 'c', power_w: 50, removed_at: 5 })]), 510.2);
|
||||
assert.equal(V.effText(V.effOf(card({ eff_mhw: 0.422 }))), '0.42 MH/W');
|
||||
assert.equal(V.effText(V.effOf(card({ eff_mhw: 0, power_w: 0 }))), '');
|
||||
});
|
||||
|
||||
test('the tune line: never run, running with a step and a bar, tuned with the point and the next check, the prior, measured only, pinned, stopped, fleet pause, no control', () => {
|
||||
const nv = (over) => card({ sweep_state: 'idle', sweep_note: '', sweep_pct: 0, sweep_at: 0, tune_line: '', tune_source: '', tune_clock_mhz: 0, tune_control: true, pinned: false, tune_step: 0, tune_steps: 0, tune_eta_s: 0, ...over });
|
||||
const idle = V.tuneWords(nv(), settings(), NOW);
|
||||
assert.equal(idle.kind, 'idle'); assert.equal(idle.text, 'Not tuned yet: starts after 2 min of steady mining'); assert.equal(idle.button, 'tune'); assert.equal(idle.progress, -1);
|
||||
assert.equal(V.tuneWords(nv(), settings({ sweep: false }), NOW).text, 'Not tuned yet: Ember Tune is off in Settings');
|
||||
const run = V.tuneWords(nv({ state: 'tuning', tune_step: 7, tune_steps: 9, tune_eta_s: 230 }), settings(), NOW);
|
||||
assert.equal(run.kind, 'running'); assert.equal(run.text, 'Tuning: step 7 of 9 · about 4 min left'); assert.equal(run.button, 'stop'); assert.equal(run.progress.toFixed(2), '0.67');
|
||||
const runNoStep = V.tuneWords(nv({ sweep_state: 'running', sweep_note: 'tuning: waits for 120 s of steady mining' }), settings(), NOW);
|
||||
assert.equal(runNoStep.text, 'Tuning: waits for 120 s of steady mining');
|
||||
const tuned = V.tuneWords(nv({ tune_line: 'Tuned: 122.3 MH/s at 290 W (0.422 MH/W)', tune_source: 'full', tune_clock_mhz: 2470, sweep_pct: 100, sweep_at: NOW - 7200 }), settings(), NOW);
|
||||
assert.equal(tuned.kind, 'tuned'); assert.equal(tuned.note, '122.3 MH/s at 290 W (0.422 MH/W)'); assert.equal(tuned.button, 'tune');
|
||||
assert.match(tuned.text, /^Tuned 2 h ago · 2470 MHz at 100% · next check /);
|
||||
const prior = V.tuneWords(nv({ tune_line: 'Tuned: 122.3 MH/s at 290 W (0.422 MH/W)', tune_source: 'confirm', tune_clock_mhz: 2470, sweep_pct: 100, sweep_at: NOW - 120 }), settings(), NOW);
|
||||
assert.match(prior.text, /^Tuned 2 min ago from the fleet prior, confirmed · 2470 MHz at 100%/);
|
||||
const measured = V.tuneWords(nv({ tune_line: 'Tuned: 26.7 MH/s at 38 W (0.703 MH/W)', tune_source: 'baseline', tune_control: false, sweep_at: NOW - 600, sweep_note: 'measure only on Apple silicon: the system sets the clocks and the power; no control exposed' }), settings(), NOW);
|
||||
assert.equal(measured.kind, 'measured'); assert.equal(measured.text, 'Measured 26.7 MH/s at 38 W (0.703 MH/W) as it runs, 10 min ago');
|
||||
assert.equal(measured.note, 'Measured only on Apple silicon: the system sets the clocks and the power; no control exposed');
|
||||
const off = V.tuneWords(nv({ tune_control: false, sweep_note: 'measure only until Power control is on in Settings (Windows asks for administrator rights once)' }), settings({ power_control: false }), NOW);
|
||||
assert.equal(off.kind, 'idle'); assert.equal(off.note, 'Measured only until Power control is on in Settings (Windows asks for administrator rights once)');
|
||||
const pinned = V.tuneWords(nv({ tune_line: 'Tuned: 120 MH/s at 300 W (0.400 MH/W)', tune_source: 'full', pinned: true, power_pct: 80, sweep_at: NOW - 60 }), settings(), NOW);
|
||||
assert.match(pinned.text, /^Your cap stays pinned at 80% · Tuned 1 min ago/);
|
||||
const stopped = V.tuneWords(nv({ sweep_note: 'tuning stopped: a remote job took the GPU' }), settings(), NOW);
|
||||
assert.equal(stopped.kind, 'stopped'); assert.equal(stopped.text, 'Tuning stopped: a remote job took the GPU');
|
||||
const paused = V.tuneWords(nv(), settings({ tuning_off: true, tuning_note: 'tuning paused fleet-wide by the signed manifest' }), NOW);
|
||||
assert.equal(paused.kind, 'paused'); assert.equal(paused.button, '');
|
||||
assert.equal(V.tuneWords(card({ vendor: 'other', kind: 'integrated', sweep_supported: false }), settings(), NOW).kind, 'none');
|
||||
assert.equal(V.tunable(card({ vendor: 'apple', kind: 'apple' })), true);
|
||||
assert.equal(V.tunable(card({ removed_at: 5 })), false);
|
||||
});
|
||||
|
||||
test('the next check is a weekday inside a week, a date beyond it, due now when overdue', () => {
|
||||
assert.equal(V.nextCheck(0, 604800, NOW), '');
|
||||
assert.equal(V.nextCheck(NOW - 700000, 604800, NOW), 'next check due now');
|
||||
assert.equal(V.nextCheck(NOW - 604800 + 3600, 604800, NOW), 'next check within a day');
|
||||
const inThree = V.nextCheck(NOW - 604800 + 3 * 86400, 604800, NOW);
|
||||
assert.match(inThree, /^next check (Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday)$/);
|
||||
assert.match(V.nextCheck(NOW, 604800 * 3, NOW), /^next check \d{1,2} [A-Z][a-z]{2}$/);
|
||||
assert.equal(V.nextCheck(NOW - 100, 0, NOW), V.nextCheck(NOW - 100, 604800, NOW), 'no period means seven days');
|
||||
});
|
||||
|
||||
test('the Tuning card schedule and the goal consequence', () => {
|
||||
const nv = (over) => card({ sweep_at: 0, tune_steps: 0, ...over });
|
||||
assert.equal(V.schedule([nv()], settings(), NOW), 'Not tuned yet: the first tune starts 2 min into steady mining.');
|
||||
assert.equal(V.schedule([nv()], settings({ sweep: false }), NOW), 'Ember Tune is off. Tune and Tune all still work by hand.');
|
||||
assert.equal(V.schedule([nv({ state: 'tuning', tune_step: 2, tune_steps: 9, tune_eta_s: 500 })], settings(), NOW), 'Tuning now: NVIDIA GeForce RTX 5090, step 2 of 9, mining again in about 8 min.');
|
||||
assert.match(V.schedule([nv({ sweep_at: NOW - 7200 }), nv({ key: 'b', name: 'B', sweep_at: NOW - 60 })], settings(), NOW), /^Last tune 1 min ago \(B\) · next check /);
|
||||
assert.equal(V.schedule([], settings(), NOW), 'No card here can be tuned.');
|
||||
assert.equal(V.schedule([nv()], settings({ tuning_off: true, tuning_note: 'tuning paused fleet-wide by the signed manifest' }), NOW), 'tuning paused fleet-wide by the signed manifest');
|
||||
const g = V.goalWords('efficiency', [card({ sweep_watts: 290, power_w: 410 })], 28);
|
||||
assert.equal(g.watts, 290); assert.equal(g.consequence, 'about £1.95 a day at 290 W');
|
||||
assert.equal(V.goalWords('rate', [card({ power_default_w: 575 })], 28).watts, 575);
|
||||
assert.equal(V.goalWords('balanced', [card()], 28).watts, 410);
|
||||
assert.equal(V.goalWords('nonsense', [card()], 0).goal, 'balanced');
|
||||
assert.equal(V.goalWords('balanced', [card()], 0).consequence, 'about 410 W');
|
||||
});
|
||||
|
||||
test('the prove tier sentence per card, and the counts in one line', () => {
|
||||
assert.equal(V.proveTier(card()), 'NVIDIA GeForce RTX 5090: proves while it mines (32 GB; a full shard needs 20.4 GB).');
|
||||
assert.equal(V.proveTier(card({ name: 'NVIDIA GeForce RTX 4070', vram_mb: 12288 })), 'NVIDIA GeForce RTX 4070: cannot prove a full shard (12 GB; a full shard needs 20.4 GB).');
|
||||
assert.equal(V.proveTier(card({ name: 'Apple M5 Max', vendor: 'apple' })), 'Apple M5 Max: proves on the CPU, slowly.');
|
||||
assert.equal(V.proveTier(card({ name: 'AMD Radeon RX 9070 XT', vendor: 'amd', vram_mb: 16384 })), 'AMD Radeon RX 9070 XT: cannot prove yet (the prover is CUDA only).');
|
||||
assert.equal(V.proveLine({ assigned: 0, submitted: 0, paid: 0, paid_wei: '0' }, false, true), 'Off · not proving · 0 assigned · 0 proven · 0 paid · 0.0000 IGN');
|
||||
assert.equal(V.proveLine({ status: 'idle', assigned: 3, submitted: 2, paid: 1, paid_wei: '1500000000000000000', available: true, enabled: true }, true, true), 'Idle · nothing assigned · 3 assigned · 2 proven · 1 paid · 1.5000 IGN');
|
||||
});
|
||||
|
||||
test('the node line, the activity line, the asks', () => {
|
||||
const n = { state: 'synced', blocks: 109859, headers: 109859, peers: 6, daa: 198000, last_reading_age_s: 5, message: '' };
|
||||
const l = V.nodeLine(n, { severity: 'none' }, '');
|
||||
assert.equal(l.text, 'Node synced · 6 peers · 109,859 blocks'); assert.equal(l.tone, 'ok'); assert.match(l.sub, /every block/);
|
||||
assert.equal(V.nodeLine({ ...n, peers: 1, blocks: 0, state: 'starting' }, { severity: 'none' }, '').text, 'Node starting · 1 peer');
|
||||
assert.equal(V.nodeLine(n, { severity: 'block', skew_s: -412 }, '').tone, 'bad');
|
||||
assert.equal(V.eventLine('consensus parameters from the signed manifest: {"difficulty_v2_activation_daa":33000}'), 'consensus parameters from the signed manifest (details in the log)');
|
||||
assert.equal(V.eventLine('node synced: 151512 blocks, 6 peer(s)'), 'node synced: 151512 blocks, 6 peer(s)');
|
||||
assert.equal(V.eventLine('a '.repeat(100), 40).length <= 41, true);
|
||||
assert.equal(V.addressAsk('0x1234567890abcdef1234567890abcdef12345678'), 'Pay 0x1234…5678 from the next block? The miner restarts.');
|
||||
assert.match(V.trustAsk(true), /^Include proof records the node never checked\? Devnet only; the node restarts\.$/);
|
||||
assert.match(V.trustAsk(false), /^Verify proof records again\?/);
|
||||
assert.equal(V.ago(30), 'just now'); assert.equal(V.ago(7200), '2 h ago'); assert.equal(V.ago(200000), '2 d ago');
|
||||
});
|
||||
|
|
|
|||
283
docs/plans/miner-ui-3-audit.md
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
# Igneum Miner UI 3: the audit, 6 October 2026
|
||||
|
||||
the project lead, 18:00 UTC: "run another audit of the miner app and make it as gorgeous as possible and as simple as possible but
|
||||
not lacking any features, think of this as an Apple product in terms of UI and UX, with tuning easy, buttons to tune
|
||||
each card again, or if automated then fair enough." Branch `miner-ui-3`, worktree `../igneum-wt-miner-ui-3`, from
|
||||
master b79f2cb. The design is `docs/plans/miner-ui-3.md`; this file is what was found.
|
||||
|
||||
## 1. How it was looked at
|
||||
|
||||
| What | How |
|
||||
|---|---|
|
||||
| The installed 0.3.13 app on this Mac | its URL from `~/Library/Application Support/Igneum/app/app.url`, opened in the built-in browser pane and shot headless with Playwright's own chromium (never Chrome.app). The miner is paused on the project lead's order and was not resumed; `/api/state` read only. |
|
||||
| The states this Mac cannot show (an NVIDIA rig, syncing, a node restart, a clock block, no cards, the setup screens) | `tools/ui-mock/server.mjs` on port 4317 from this worktree (the one on 4310 is another agent's and serves the old one-page UI; do not read it as master), scenarios `fresh`, `nvidia`, `syncing`, `nodedown`, `nocards`, `clock`. |
|
||||
| The update card and the strips | the UI's own `?update=` and `?job=` sample states on the live app (client side only, nothing posted). |
|
||||
| Sizes | 1280 x 820 (the shipped window), 900 x 600 (the hosts' floor), 390 x 844 (a narrow Windows window, a phone). |
|
||||
|
||||
Screenshots: `docs/plans/miner-ui-3/00-*.png` to `26-*.png`, named below. The live app's state while shooting: Apple
|
||||
M5 Max, paused, node synced at 109,859 blocks with 6 peers, 23,056 lifetime blocks, proving off, external node.
|
||||
|
||||
## 2. What a first-time home miner sees, screen by screen
|
||||
|
||||
Rating: 10 = nothing to change. The three questions per screen: what does it say in jargon, which number has no
|
||||
unit or no meaning, which 0 or blank has no reason word.
|
||||
|
||||
### 2.1 Welcome (`00-welcome.png`), 7/10
|
||||
|
||||
Good: one heading, one lead, three tiles, one button. The eyebrow "devnet v4 · nothing is bought or sold" is honest.
|
||||
|
||||
| Problem | Where |
|
||||
|---|---|
|
||||
| "Mined by GPUs. Proven by fire." is an aphorism (copy law). | lead |
|
||||
| "Syncs the chain from the seed node", "A new program every hour. The next one compiles while you mine", "EVM address", "seed" (the last line) are jargon a home miner has never met. | the three tiles, the seed line |
|
||||
| The coin is the retired ringed coin (`brand/README.md`: "the old ringed coin. Retired as a source"). The app icon is the plain square. | the hero |
|
||||
| The welcome has no idea what the user will earn or spend; the one question a home miner has ("is this worth running?") is unanswered until the third screen. | whole screen |
|
||||
|
||||
Apple would: keep one line of what it does, three tiles in plain words (runs in the background, uses your graphics
|
||||
card, pays an address you own), the button. Drop the seed line (there is no seed phrase in this app; it is a key).
|
||||
|
||||
### 2.2 Set up, step 1, your GPU (`01-setup-gpu.png`), 7/10
|
||||
|
||||
Good: one row per card, the switch, a sentence. The integrated-GPU rule is explained.
|
||||
|
||||
| Problem | Where |
|
||||
|---|---|
|
||||
| "worker Metal", "40 GPU cores", "64 GB unified": three facts, none the user chose or can change. "worker" is internal. | the row's meta line |
|
||||
| "Each card you switch on gets its own worker" is engine talk. | the help note |
|
||||
| The NVIDIA note about the 80% cap and the administrator prompt appears only when an NVIDIA card is present; that is right, but it is 44 words for a decision the user is not taking here. | `#cards-power` |
|
||||
| No number: the row does not say what the card is expected to make. The fleet priors (`site/miner-priors.json`) know the model's MH/s and W; the row could say "about 120 MH/s at 290 W" for a known model. | the row |
|
||||
|
||||
Apple would: one row, name, switch, one line under the list. The cap note goes to the card where it applies.
|
||||
|
||||
### 2.3 Set up, step 2, where rewards go (`02-setup-address.png`), 8/10
|
||||
|
||||
Good: two options, one recommended, the paste box opens on choice, one button.
|
||||
|
||||
| Problem | Where |
|
||||
|---|---|
|
||||
| "EVM address", "secp256k1 key", "0x followed by 40 hex characters": the first is unavoidable once, the second is jargon that helps nobody. | the lead, option 1 |
|
||||
| The dev-fee sentence sits here, on the address screen, as a surprise. It belongs where the fee is switched (Settings) and on the earnings line. | the lead |
|
||||
| "Start mining" on this screen does not start mining when "Make me an address" is chosen: the key sheet comes first. The button lies once. | the primary button |
|
||||
|
||||
### 2.4 The key sheet (`03-setup-key.png`), 8/10
|
||||
|
||||
Good: shown once, both values with Copy, the file path, the checkbox gate.
|
||||
|
||||
| Problem | Where |
|
||||
|---|---|
|
||||
| "On devnet the vote keys are test keys derived from the miner's label. Mainnet vote keys will be random and stored like this wallet." is an internal note on a user's first sheet. | the second note |
|
||||
| "Settings can show it again": the key lives on Rewards, not Settings (`r-key-card`). Wrong page named. | the first note |
|
||||
| The address box repeats what the next screen shows; the sheet could show the key alone, with the address beneath in small. | the two boxes |
|
||||
|
||||
### 2.5 Mine (`04-mine-live.png`, rig: `15-mine-nvidia-rig.png`), 6/10
|
||||
|
||||
What a home miner sees first after setup. Good: the big button, the rate, the rows, the blocks strip.
|
||||
|
||||
| Problem | Where |
|
||||
|---|---|
|
||||
| Four tiles compete with the big button: hash rate, blocks found, next program. "Next program 49:52 · next program not known yet (…)" is a countdown to an internal event, cut off mid-sentence. A home miner does not need an epoch timer on the first screen. | hero row |
|
||||
| The card row's numbers are `n/a` three times on this Mac (hash rate, temperature, power), in ember, which reads as an error. Apple silicon reports no temperature or power, so the words should be "not reported on Apple silicon", once, not three red `n/a`. | GPU row |
|
||||
| The row says "off · 64 GB unified" while the switch is ON and the big button says "Start mining": three states on one row. The card is on; the miner is paused. The row needs the word "paused". | GPU row |
|
||||
| 0.0 MH/s in ember with "paused" under it: the number is right, but the eye reads a red zero. The rule (the project lead, 6 October 2026, ember-tune.md 6a): a card never shows 0 MH/s without a reason word. The tile does carry "paused", so it passes, but in grey, under a red 0. | hash rate tile |
|
||||
| No money anywhere. The page shows MH/s, W, °C, blocks. It never says what a block is worth, what the electricity costs, or what the machine made today. The one number a home miner wants is missing. | whole page |
|
||||
| MH/W (efficiency) is on Settings, not on the row where the rate and the watts are. | GPU row |
|
||||
| "Your blocks": "last 10 min 0 · last hour 0 · dev fee 0 · chain 109.8k". Four bare counts; "chain 109.8k" is the chain height with no label of what it means; "dev fee 0" is the count of blocks mined for the dev fee, with no unit word (the tooltip has it). | blocks card head |
|
||||
| The blocks strip is 170 px of empty canvas for most of a home miner's day (one block an hour on a small card). The legend has three items for one kind of mark. | blocks card |
|
||||
| Activity shows raw engine lines: "consensus parameters from the signed manifest: {"difficulty_v2_activation_daa":33000, ..." as a JSON blob, "remote job: update check now; a newer version installs at once". The feed is a developer log, not a user's activity. | Activity |
|
||||
| The rig shot: the 4070 row says "restart in 4 s · 12 GB · 6783 blocks · ne…" and the hash reads `n/a` in ember; the second sentence is cut. The reason for the restart (the engine's `message`) is not on the row. | 15 |
|
||||
| "A power cap is not applied yet: Settings has a Retry for the administrator prompt." A problem on the Mine page sends the user to another page to fix it. Two taps minimum. | the note under the rows |
|
||||
| The top bar repeats the pill: "paused · node synced · up 24 min" and then a pill saying PAUSED. | top bar |
|
||||
| "1 OF 1 ON" eyebrow in capitals is a count of switches, not of cards mining. | card head |
|
||||
|
||||
Apple would: one hero (the big button and the live total: MH/s, W, £ a day), the card rows as the only list, a
|
||||
one-line "today" (blocks, £, kWh), and move the blocks strip and the activity feed behind one "Activity" view.
|
||||
|
||||
### 2.6 Prove (`05-prove-live.png`), 5/10
|
||||
|
||||
| Problem | Where |
|
||||
|---|---|
|
||||
| 95 words of explanation before the switch: "short mathematical proof, in pieces called shards. The chain assigns shards to your keys ... a full shard needs 20.4 GB of GPU memory, measured". A home miner wants one sentence: can my card do this, and do I earn from it. | lead card |
|
||||
| Two paragraphs say "Off" twice ("Off. Switch it on and this machine proves..."). | lead card |
|
||||
| Four tiles with 0: state Off, assigned 0, proven 0, paid 0 "0.0000 IGN earned". Three zeros with no reason word. The reason is on the first tile ("off") and nowhere near the others. | the strip |
|
||||
| "Verifier: off: relay only" in ember, then 50 words on what a verifier is, then "external node: the app did not start it, so it set no verifier". Internal plumbing. | Verifier card |
|
||||
| "Program · pinned guest", "shard program id", "aggregator id": two 66-character hashes with Copy. Nobody at home copies these. | Program card |
|
||||
| "Proving on; the first shard arrives within a minute" toast, "needs setup, one-time install, about 20 minutes" row: the only user-facing decision (install the prover) is a small button in the lead card. | pv-setup |
|
||||
|
||||
Apple would: one card. A tier sentence per card ("RTX 5090 proves while it mines", "RTX 4070 (12 GB) cannot prove: a
|
||||
proof needs 20.4 GB", "Apple M5 Max proves on the CPU, slowly"), the switch, and one earnings line ("0.0000 IGN from 0
|
||||
proofs"). The ids and the verifier go behind "Details".
|
||||
|
||||
### 2.7 Rewards (`06-rewards-live.png`), 6/10
|
||||
|
||||
| Problem | Where |
|
||||
|---|---|
|
||||
| "Balance: --, shown in the wallet, not here yet". A placeholder on the money page. The engine has no balance field (miner-ui-2 follow-up, never built). | balance tile |
|
||||
| "This run: 0 since the app started, 24 min a…" cut off. | tile |
|
||||
| No money. Blocks found 23,056 with no value. No reward per block, no IGN total, no £. | whole page |
|
||||
| "Save your key" is a card with ember "ONCE" every time the page opens, even after the key is saved and acknowledged. | key card |
|
||||
| "Use another address" is an open text box with Change next to it, on the same screen as the key. Pasting a wrong address and tapping Change moves every future block with no confirmation. | address card |
|
||||
| The dev-fee line sits under the address box, in the "Use another address" card, where it has nothing to do. | r-devfee-line |
|
||||
| "Open the wallet page" is a ghost button at the bottom of the second card. The wallet is where the balance is. | r-wallet |
|
||||
|
||||
Apple would: one card, money first (IGN earned, blocks found, £ a day at today's rate when a price exists), the
|
||||
address with Copy, then "Change address" and "Show my key" as two rows that confirm in place.
|
||||
|
||||
### 2.8 Node (`07-node-live.png`, syncing `18-node-syncing.png`, clock `21-node-clock.png`), 5/10
|
||||
|
||||
| Problem | Where |
|
||||
|---|---|
|
||||
| Four tiles, four cards, eleven numbers: headers, DAA score, difficulty, tips, blue score, digest, next switch, last lock, age, votes sent, peers. A home miner needs one line: "the node is synced, 6 peers". | whole page |
|
||||
| "DAA score", "blue score", "tips", "difficulty 137.73M", "consensus digest not printed yet", "finality miner-only", "last lock none yet", "age n/a", "votes sent 0 · waiting for the miner": every one is jargon or a 0 without a reason (the finality 0 has one, in a note, in grey). | Chain, Consensus, Finality |
|
||||
| "Synced" appears three times (the tile, the Sync card, the top bar). | tiles, Sync card |
|
||||
| The Sync card repeats the node tile's sentence word for word. | Sync card |
|
||||
| The clock block (21) is right in shape (a card with Sync clock and the manual hint) but sits on the Node page; a home miner whose clock is wrong is looking at Mine, where the hash is 0. The rule here says the strip carries the clock only on the setup screens. | n-clock |
|
||||
| "Read 5 s ago", "read every 10 s": two staleness marks for one reading. | tile, Chain eyebrow |
|
||||
|
||||
Apple would: one line on Mine ("Node synced · 6 peers · 109,859 blocks") and a Details view for the rest, since the
|
||||
digest and the switches are needed when something is wrong, not every day.
|
||||
|
||||
### 2.9 Updates (`08-updates-live.png`, job strips `13`, `14`), 6/10
|
||||
|
||||
| Problem | Where |
|
||||
|---|---|
|
||||
| "Consensus upgrade at height 198,000." under the version with no meaning (the height is behind the node already: 198,490). | the lead line |
|
||||
| The remote-jobs card: 60 words of explanation, a Check now, a 20-row table with columns job / kind / started / exit / report, job ids like `update-now-0313-switch-d937c69d`, "done (0)", "uploaded". This is the fleet operator's console, shown to every user. | Remote jobs |
|
||||
| "signing key sha256:8f186e37b48…" at the foot. | s-jobs-key |
|
||||
| Two "Check now" buttons on one page, one per card, both the same words. | both cards |
|
||||
| The switch "Install updates by itself" and the paragraph under it are right. | lead card |
|
||||
|
||||
Apple would: one card: the version, "up to date, checked 13 min ago", the switch, Check. The jobs go into a history
|
||||
row ("Igneum ran 20 jobs on this machine, the last 16:36") that opens the table.
|
||||
|
||||
### 2.10 Settings (`09-settings-live.png`, rig `16-settings-nvidia-rig.png`), 4/10
|
||||
|
||||
The page with the most work in it and the hardest to use.
|
||||
|
||||
| Problem | Where |
|
||||
|---|---|
|
||||
| The per-card block mixes a slider, two status lines, a tune line with a button, and an identities stepper with 30 words of help, per card. On the rig shot each card is 370 px tall; three cards fill two screens. | set-card |
|
||||
| "Power cap 80% · 460 W", "cap applied: 460 W", "draw 441 W GPU 71 °C memory 92 °C efficiency n/a": the same watts three times; efficiency `n/a` while draw and rate are both known (the engine's `eff_mhw` was 0 in the mock; live it is computed). | set-card lines |
|
||||
| "tuning: not run yet (starts after 120 s of steady mining)" with a Tune now button; nothing says when the next scheduled tune is, or when the last one ran. | tune line |
|
||||
| Ember Tune's switch has a 118-word paragraph under it: the power ladder, the clock ladder, 75 s a step, 1% tolerance, rejected hashes, hot GPUs, dragged memory clocks, fleet priors, privacy. This is the engineering spec, on a switch. | s-sweep help |
|
||||
| Power control: three sentences, then the same sentence again from the state ("Off: NVIDIA cards measure only; AMD cards need no rights." twice in the live shot). | s-power-control help |
|
||||
| "Identities: Each identity votes and is paid on its own. 8 suits a big card, 2 a small one, 1 an integrated GPU. Changing it restarts that card's worker." A stepper 1 to 64 for a concept the user cannot picture. The engine picks a default per card already. | ids |
|
||||
| "cap not applied (needs the administrator prompt) Retry" in ember: this is the one thing a Windows NVIDIA user must do, and it is a tiny button inside the second card's fourth line. | 16, the 4070 |
|
||||
| "name: a name for this machine · Rename. A label for you only. Keys come from the machine id d937c69d, never from the name." Jargon for a text box. | This machine |
|
||||
| "Vote on finality checkpoints: Your miner signs a checkpoint every 30 s. Votes are what lock the chain; leave it on." A switch whose help says never to touch it. | s-vote |
|
||||
| "Trust proof records without verifying them" under Advanced: devnet only, restarts the node. | s-trust |
|
||||
| "Dev fee 1% (1 block in 100)" with a switch: right, but the fee's own line is "Dev fee 1% (1 block in 100); the miner names the address when it starts". | s-devfee |
|
||||
| Logs: two buttons and two file paths. Right, but the log is also in the rail. | Logs card |
|
||||
| No electricity price anywhere, so nothing in the app can say £. | whole page |
|
||||
|
||||
Apple would: Settings as a list of plain rows: Tuning (goal, Power control, schedule), Start at login, Updates,
|
||||
Remote jobs, Dev fee, Electricity price, This machine's name, Logs, Advanced. The per-card controls live on the
|
||||
card, on Mine, where the numbers are.
|
||||
|
||||
### 2.11 The update card (`11-update-card-ready.png`), 9/10
|
||||
|
||||
The best screen in the app: the mark with a ring, the name, one line, three notes, the size, two buttons. It says
|
||||
"Igneum Ember 0.3.7" while the app is "Igneum Miner" everywhere else (`UpdateCard.NAME`). Keep it, fix the name.
|
||||
|
||||
### 2.12 The notices strip (`12-strip-update-downloading.png`, `13-strip-job-running.png`, `14-strip-job-failed.png`), 7/10
|
||||
|
||||
One notice at a time, actions on the right, a close. Right. Two problems: the job strip shows raw `RESULT shard=2
|
||||
prove_s=39.8` lines as the detail (engine output to a user), and the strip pushes the whole page down 100 px when it
|
||||
appears (the hero row jumps).
|
||||
|
||||
### 2.13 The log drawer (`10-logs-drawer-live.png`), 8/10
|
||||
|
||||
Chips, search, a time jump, last error, last swap, wrap, copy, follow, a ruler. Complete and quick (virtualised).
|
||||
It is a developer's tool and it is fine as one. "WATCH" as a source chip means nothing to a user; the lines under it
|
||||
are the observer's. Keep, rename nothing, hide behind Logs as now.
|
||||
|
||||
### 2.14 The tray and the menu bar item
|
||||
|
||||
macOS (`app/mac/IgneumMiner.swift`): Open Igneum Miner, Pause mining / Resume mining, "Node: synced, 109,859 blocks,
|
||||
6 peers", "Blocks found: 23,056", Quit Igneum Miner. Windows (`app/windows/host.cpp`): the tip "Igneum Miner: 17.0
|
||||
MH/s, 8,058 blocks found, node synced", a menu with Open, Pause and Quit, and a balloon "The window is in the tray.
|
||||
Right-click the icon to pause or quit." Both fine. Neither shows money or watts; both get them when the state has
|
||||
them (the Rust side formats the tip from `hash_total`, `accepted_total`, `node.state`; a `watts_total` and a
|
||||
`pounds_per_day` field would complete it; owed, not built here).
|
||||
|
||||
### 2.15 Narrow (`22-narrow-900-mine.png`, `23`, phone `24-phone-mine.png`, `25`)
|
||||
|
||||
900 x 600: the rail folds to icons, the hero goes two by two, the rows drop the power column. Usable. 390 wide: the
|
||||
rail still takes 150 px, "Stop mining" is cut to "Stop minin", the GPU row's numbers overflow the card to the right
|
||||
(`413` half off screen), the tiles truncate every sub-line. The Windows window can be dragged to 400 px wide and the
|
||||
layout breaks there. No phone layout exists.
|
||||
|
||||
### 2.16 Light mode (`26-light-scheme-mine.png`)
|
||||
|
||||
There is none. `<meta name="color-scheme" content="dark">`, one token set on `:root`, no `prefers-color-scheme`
|
||||
block. The page is dark whatever the OS says.
|
||||
|
||||
## 3. Every feature the old UI has (the build's tick list)
|
||||
|
||||
| # | Feature | Where it is today | Route |
|
||||
|---|---|---|---|
|
||||
| 1 | Welcome, GPU pick, address choice, key sheet | the three setup screens | `api/detect`, `api/cards`, `api/setup`, `api/key/saved`, `api/start` |
|
||||
| 2 | Start / stop mining | Mine big button | `api/pause`, `api/resume` |
|
||||
| 3 | Per-card on/off switch | Mine row, setup row | `api/cards` (the whole list) |
|
||||
| 4 | Hash rate, temperature, power per card; totals | Mine row, hero tiles | state |
|
||||
| 5 | Blocks found, this run, last 10 min, last hour, dev fee count, chain height | Mine tiles and blocks card | state |
|
||||
| 6 | Next program countdown and epoch | Mine tile | `program` |
|
||||
| 7 | Blocks strip (10 min canvas) | Mine | state `found` |
|
||||
| 8 | Activity feed | Mine | `events` |
|
||||
| 9 | Hot-plug states: new card, removed, not usable (Code 43) | Mine row, strip | state |
|
||||
| 10 | Proving switch, setup button, state/assigned/proven/paid, segments line, verifier, program ids | Prove | `api/prove`, `api/prove/setup` |
|
||||
| 11 | Rewards address with Copy, source, blocks, this run, balance placeholder | Rewards | state |
|
||||
| 12 | Show / hide the private key, the wallet file path | Rewards | `api/key/reveal` |
|
||||
| 13 | Change the rewards address | Rewards | `api/settings {address}` |
|
||||
| 14 | Open the wallet page | Rewards | `api/open` |
|
||||
| 15 | Node state, height, peers, version, chain numbers, digest with Copy, switches list, next switch, finality, sync ETA | Node | state |
|
||||
| 16 | Clock skew card with Sync clock and the manual hint | Node, setup strip | `api/clock/sync` |
|
||||
| 17 | Version, update note, Check now, Install now, auto-update switch | Updates | `api/update/check`, `install`, `auto`, `open` |
|
||||
| 18 | The update card (modal) and the strip notices | everywhere | same |
|
||||
| 19 | Remote jobs: allow switch, Check now, note, history table, signing key | Updates, Settings | `api/jobs/allow`, `api/jobs/check` |
|
||||
| 20 | Per-card power cap slider (NVIDIA), applied / not applied + Retry, draw and temperatures, efficiency | Settings card | `api/cards {power_pct}`, `api/power/apply` |
|
||||
| 21 | Per-card identities stepper | Settings card, setup row | `api/cards {identities}` |
|
||||
| 22 | Ember Tune switch, per-card Tune now / Stop / Unpin, the tune line | Settings | `api/sweep/enable`, `start`, `stop`, `pin` |
|
||||
| 23 | Power control switch | Settings | `api/power/control` |
|
||||
| 24 | Start at login, vote, dev fee, machine name, trust mode, live page | Settings | `api/settings` |
|
||||
| 25 | Copy the log, show the log, the log and chain folders | Settings | `api/log` |
|
||||
| 26 | The log drawer (chips, search, time, last error, last swap, wrap, copy, follow, ruler, resize) | rail | `api/log` |
|
||||
| 27 | Quit (with a confirm dialog) | rail | `api/quit` |
|
||||
| 28 | The pill, the top status, the rail foot (name, address, version, chain) | chrome | state |
|
||||
| 29 | Keyboard: arrow keys on the rail, Escape on the card, Enter on the time box | chrome | none |
|
||||
| 30 | `?page=`, `?screen=`, `?update=`, `?job=`, `?logs=1`, `?visible=1`, `?debug=1`, `?host=mac` | URL | none |
|
||||
|
||||
Ember's new fields (ember-tune e9738dd, from the agent): `tune_step`, `tune_steps`, `tune_eta_s`, `tune_plan`
|
||||
(full | confirm | baseline | climb), `tune_curve`, `tune_mem_mhz`, `mem_cap_mhz`, card states `tuning` and `held`;
|
||||
`settings.tune_goal` (efficiency | balanced | rate), `settings.power_price_pence`, `settings.tune_climb`,
|
||||
`settings.tune_period_s`; `POST /api/tune/goal {goal, price_pence, climb}`. No next-tune field: next = `sweep_at +
|
||||
tune_period_s`, "due now" when `sweep_at` is 0.
|
||||
|
||||
## 4. The top ten findings
|
||||
|
||||
| # | Finding | Consequence per tier |
|
||||
|---|---|---|
|
||||
| 1 | No money anywhere. No electricity price, no £ a day, no IGN earned on Mine or Rewards. | Every tier. A home miner with one card cannot tell whether the app pays for its power; a rig owner cannot compare cards. |
|
||||
| 2 | Three red `n/a` per card row on Apple silicon, 0.0 MH/s in ember, "off" on a row whose switch is on. A paused Mac looks broken. | macOS, every card. Apple silicon never reports W or °C, so every Mac user sees this forever. |
|
||||
| 3 | Tuning is split: the schedule and the switch on Settings, the row on Mine, the tune line per card on Settings, no "tuned 2 h ago, next Sunday", no Tune all, no goal. | Every tier with a tunable card (NVIDIA, AMD on Windows). |
|
||||
| 4 | Settings per-card block is 370 px per card: slider, identities, tune line, telemetry. A six-card rig scrolls through 2,200 px of controls. | Rigs most; single cards get a page that looks like a rig's. |
|
||||
| 5 | Prove is 95 words plus four zeros plus two hashes. The tier sentence ("this card cannot prove: 12 GB") is not said anywhere. | 8, 12 and 16 GB cards see a switch that will fail; 24 and 32 GB cards do not see that they can. |
|
||||
| 6 | Node is eleven numbers for one fact. DAA, blue score, tips, digest, lock, votes: all jargon with no reason words. | Every tier; nothing to do per tier. |
|
||||
| 7 | The remote-jobs history table and the signing key are on every user's Updates page. | Every tier sees the fleet console. |
|
||||
| 8 | Fixes live on another page: "cap not applied, Settings has a Retry", "Switch a GPU on below first", "Settings turns it off". Two taps for every fix. | Windows NVIDIA most (the administrator prompt). |
|
||||
| 9 | No light mode; no layout under 900 px (the Windows window can be narrower; the rail eats 150 px at 390). | Windows laptop users with a half-screen window. |
|
||||
| 10 | Developer lines leak into user surfaces: the Activity feed's JSON, the job strip's `RESULT` lines, "Igneum Ember" on the update card, "worker Metal", "pinned guest", "miner-only", "shown in the wallet, not here yet". | Every tier. |
|
||||
|
||||
Smaller, fixed in the build with no mention elsewhere: the key sheet says "Settings can show it again" (it is
|
||||
Rewards); "Start mining" on the address screen opens the key sheet instead; "Synced" three times on Node; two
|
||||
"Check now" on Updates; "Once" eyebrow on the key card forever; the retired ringed coin on Welcome; "Mined by GPUs.
|
||||
Proven by fire." (aphorism); the quit `confirm()` dialog (the viewer never shows dialogs: the rule in the brief).
|
||||
|
||||
## 5. What the previous redesign (miner-ui-2) set out to do and what it achieved
|
||||
|
||||
It set out to split one page into sections, put every setting on a section with one line of help, and lay out from
|
||||
900 x 600. It achieved all three: the rail works, the keyboard works, nothing was lost, the update card and the
|
||||
strip are good. What it did not do, by its own section 4: the balance, the pause-while-I-use-it switch, the log
|
||||
export. What it did not see: it moved the controls off the cards and on to a page, so the thing the user tunes (a
|
||||
card) and the controls that tune it are two sections apart; it kept the engine's vocabulary on every page; and it
|
||||
never put a currency on screen. UI 3 keeps the rail's sections as views, puts the card back at the centre, and
|
||||
adds the money.
|
||||
277
docs/plans/miner-ui-3.md
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
# Igneum Miner UI 3: the card is the product
|
||||
|
||||
6 October 2026. the project lead, 18:00 UTC: "as gorgeous as possible and as simple as possible but not lacking any features,
|
||||
think of this as an Apple product in terms of UI and UX, with tuning easy, buttons to tune each card again, or if
|
||||
automated then fair enough." The audit is `docs/plans/miner-ui-3-audit.md`. Branch `miner-ui-3`, worktree
|
||||
`../igneum-wt-miner-ui-3` from master b79f2cb. The 0.3.14 cut takes it. Ember Tune's engine (branch `ember-tune`,
|
||||
agent a855dcc4bd05e0615) is not touched: this plan reads its fields and posts to its routes.
|
||||
|
||||
## 1. The one job of each screen
|
||||
|
||||
| Screen | Its one job | What it holds | What left it |
|
||||
|---|---|---|---|
|
||||
| Welcome, GPU, address, key | get to mining in two taps with an address the user owns | as miner-ui-2, shorter words, the mark instead of the coin, the key sheet alone | the aphorism, "seed", "secp256k1", "worker", the devnet vote-key note |
|
||||
| Mine | is the machine mining, and what is it worth | the big button, the live total (MH/s, W, £ a day), the card rows, the tuning card, the node line, activity | the next-program tile (to the node details), the four bare counts, the JSON in the feed |
|
||||
| Earnings | what did it make, where does it go | money and IGN first, blocks, the address, change address, show key, the dev fee, the wallet | the balance placeholder, the "once" eyebrow |
|
||||
| Prove | can this machine prove, and is it | one switch, one tier sentence per card, one counts sentence, Set up when needed; details behind a disclosure | the 95-word lead, the four zero tiles, the two hashes on the surface |
|
||||
| Settings | the switches, one sentence each | tuning (goal, Ember Tune, Power control, electricity price), this machine, updates (one card), remote jobs (one row, history behind it), logs, appearance, advanced | the per-card controls (to the card), the spec paragraph, the second "Off" sentence |
|
||||
| Node | gone as a section | one line on Mine; "Details" opens the chain numbers, digest, switches, finality, version, clock | the eleven-number page |
|
||||
| Updates | gone as a section | one card on Settings; the update card and the strip as they are | the jobs table on the surface |
|
||||
| Logs | the engineer's view | the drawer as it is | nothing |
|
||||
|
||||
Four sections in the rail: Mine, Earnings, Prove, Settings. Logs and Quit in the foot. The rail keeps the arrow
|
||||
keys and `aria-current`.
|
||||
|
||||
## 2. The card row, the hero object
|
||||
|
||||
One row per card, ordered by performance (branch `card-order` ffb2bfa, `View.shownCards`). Everything a card can
|
||||
say is on its row; everything a card can be told is on its row or one tap under it.
|
||||
|
||||
```
|
||||
[NV] NVIDIA GeForce RTX 5090 DISCRETE 122 MH/s 290 W £1.95 a day 61 °C [Tune] (o)
|
||||
● mining · 32 GB · 3 blocks 0.42 MH/W at 28p/kWh
|
||||
Tuned 2 h ago · 2470 MHz at 100% · next check Sunday ⌄
|
||||
```
|
||||
|
||||
| Part | Rule |
|
||||
|---|---|
|
||||
| Name and model | `cd.name`, the kind pill (Discrete, Integrated, External, Apple silicon), the tooltip as today (`View.cardTitle`) |
|
||||
| State word | always present, with the reason: `mining`, `tuning: step k of n` (plus "about N min left"), `paused`, `waiting for the node`, `restarting in N s` (plus the engine's message), `starting`, `off` (plus the reason when the engine gives one), `held for a remote job: <title>`, `stopped after repeated faults`, `failed`, `not usable (Code 43)` (plus the reboot hint), `removed` |
|
||||
| Rate | `hash_now` as `122 MH/s` (one decimal under 100); blank with the state word when not mining; never a 0 |
|
||||
| Power | `power_w` as `290 W` with `0.42 MH/W` under it (`eff_mhw`, or rate over watts); a card that reports no draw (Apple silicon, an integrated card) hides the cell and the sub-line says `no power or temperature reading on Apple silicon` once |
|
||||
| Money | `£1.95 a day` = W × 24 / 1000 × price / 100 at the electricity price; with no price the cell reads `set a price` and taps to the price field; with no draw the cell is hidden |
|
||||
| Temperature | `61 °C`, amber from 86 °C (memory 90), ember from 92 °C (memory 95); hidden when not reported |
|
||||
| Switch | the card on or off, as today (`api/cards` with the whole list) |
|
||||
| Tune | `Tune` queues `api/sweep/start {key}`; during a run the button is `Stop` (`api/sweep/stop`); hidden for a card with no control and no measurement (`sweep_supported` false and vendor other) |
|
||||
| Tune line | the schedule sentence, section 4 |
|
||||
| Disclosure | the chevron opens the card's details: the power cap slider (NVIDIA, with the watt number, the applied or Retry line), identities (stepper, one sentence), pin or unpin, the memory temperature, the clocks, the device facts, the last tune's table (`tune_curve`) when there is one |
|
||||
|
||||
The rule from ember-tune.md 6a holds everywhere: a card never shows 0 MH/s without a reason word. A paused miner's
|
||||
row says `paused`; the switch stays on because the card is on. The old `n/a` cells are gone: a cell is shown with a
|
||||
number or not shown.
|
||||
|
||||
## 3. The Mine page
|
||||
|
||||
```
|
||||
[ Start mining ] 122 MH/s · 290 W · £1.95 a day · 3 blocks today
|
||||
mining on 1 of 1 card
|
||||
|
||||
Your cards [Tune all]
|
||||
row
|
||||
row
|
||||
|
||||
Tuning Efficiency | Balanced | Maximum about £1.95 a day at this goal
|
||||
Ember Tune on: once after install, then every 7 days. Last tune 2 h ago, next check Sunday.
|
||||
Power control [switch] Lets the app set NVIDIA power and clock limits. Windows asks once.
|
||||
|
||||
Node synced · 6 peers · 109,859 blocks · read 5 s ago Details ⌄
|
||||
|
||||
Activity Open the log
|
||||
strip (today's blocks on a 10-minute line)
|
||||
the last 8 events, one line each
|
||||
```
|
||||
|
||||
| Element | Rule |
|
||||
|---|---|
|
||||
| The big button | the only primary (ember) control on the page. Start mining / Stop mining / Tuning (disabled, "mining again in about N min") / Held / Stopping. `View.toggle`, with ember-tune's tuning and held states. |
|
||||
| The live total | `hash_total` MH/s; W = the sum of `power_w` over present cards; £ a day from that sum; blocks today = `found` in the last 24 h is not on the state, so "N this run" (`accepted_session`) with the lifetime count in the sub-line |
|
||||
| Tune all | queues `api/sweep/start` for every present, enabled, tunable card in list order (the engine runs one at a time). Hidden when Ember Tune is off fleet-wide (`settings.tuning_off`), where the line says why. |
|
||||
| Goal | a three-way segmented control, `settings.tune_goal` efficiency / balanced / rate (labelled Maximum), `POST /api/tune/goal {goal}`. The consequence line: at Efficiency the tuned watts (`sweep_watts` summed) at the price; at Maximum the default watts (`power_default_w` summed); Balanced between. A build without the route (0.3.13) gets a toast and the control stays on its old value. |
|
||||
| Schedule | `sweep_at` and `settings.tune_period_s` (default 604,800): "Last tune 2 h ago, next check Sunday" per the last-tuned card; "Not tuned yet: the first tune starts 2 min into steady mining" when nothing has run; "Tuning now: RTX 5090, step 7 of 9" while one runs. |
|
||||
| Power control | one switch, one sentence. Shown on Mine only when an NVIDIA card is present and control is off (the fix where the problem is); always on Settings. |
|
||||
| Node line | `View.nodeWords`; Details opens the old Node page's content in place: the chain numbers (headers, DAA score, difficulty, tips, blue score, each with its one-line meaning), the digest with Copy, the switches with the next one, finality, the version, the clock card when the clock is off (the clock card also shows above the big button when it blocks mining). |
|
||||
| Activity | the blocks canvas at 110 px with "N blocks in the last 10 min · N in the last hour"; the feed shows the newest 8 events, a line cut at 140 characters, a JSON tail replaced by "(details in the log)"; a link opens the drawer |
|
||||
|
||||
## 4. Tuning in words
|
||||
|
||||
| State | The row's tune line | Where else |
|
||||
|---|---|---|
|
||||
| never run, Ember Tune on | `Not tuned yet: starts after 2 min of steady mining` | the Tuning card's schedule line |
|
||||
| running | `Tuning: step 7 of 9 · about 4 min left` with a thin progress bar (`tune_step / tune_steps`) and the Stop button; the state word reads `tuning: step 7 of 9` and the rate stays live | the strip (ember-tune's `tuneNotice`), the big button |
|
||||
| tuned | `Tuned 2 h ago · 2470 MHz at 100% · next check Sunday`, the Tuned line's numbers (`tune_line`: 122.3 MH/s at 290 W, 0.422 MH/W) in the details | the Tuning card |
|
||||
| from the fleet prior | `Tuned 2 min ago from the fleet prior, confirmed · next check Sunday` | |
|
||||
| measure only (Apple, NVIDIA with Power control off, AMD on Linux) | `Measured 122 MH/s at 290 W (0.42 MH/W) as it runs` and the reason from `sweep_note` (`measure only on Apple silicon: the system sets the clocks and the power`) | Power control switch on Mine for the NVIDIA case |
|
||||
| pinned | `Your cap stays pinned at 80% · measured ...` with Unpin in the details | |
|
||||
| stopped | `Tuning stopped: a remote job took the GPU` until the next run | |
|
||||
| fleet pause | `Tuning paused fleet-wide by the signed manifest` on the Tuning card; the Tune buttons hidden | Settings |
|
||||
| no control, no measurement (an integrated card, vendor other) | no tune line, no button | |
|
||||
|
||||
Next check = `sweep_at + settings.tune_period_s`, as the weekday when under 7 days away, else the date; "due now"
|
||||
when `sweep_at` is 0 and the card mines. The engine also re-tunes on a driver or program change; the line does not
|
||||
predict that.
|
||||
|
||||
## 5. Earnings
|
||||
|
||||
```
|
||||
£0.00 earned IGN: nothing is bought or sold on devnet
|
||||
0.0000 IGN from 0 proofs
|
||||
23,056 blocks lifetime · 0 this run · 1 in 100 to the dev fee (0 so far) [dev fee switch]
|
||||
£1.95 a day electricity at 28p/kWh for 290 W Set the price
|
||||
|
||||
Rewards address 0xdd44…86E8 [Copy] made on this machine
|
||||
Change address ⌄ → a box, Change, then in place: "Pay 0x12…34 from the next block? Confirm Cancel"
|
||||
Show my key ⌄ → in place: "Show the private key on screen? Anyone who sees it can spend." Show Cancel
|
||||
then the key box, Copy, Hide, the file path
|
||||
[Open the wallet] the balance is in the wallet
|
||||
```
|
||||
|
||||
Money first, IGN second, as asked. Devnet has no price for IGN, so the £ earned line says why it is £0.00 instead
|
||||
of showing a placeholder; the electricity line is real money and is shown as a cost. When a market price exists the
|
||||
engine adds `price_gbp_per_ign` and the line fills in (owed, engine).
|
||||
|
||||
## 6. Prove
|
||||
|
||||
```
|
||||
Prove on this machine [switch]
|
||||
RTX 5090: proves while it mines (32 GB; a full shard needs 20.4 GB).
|
||||
RTX 4070: cannot prove a full shard (12 GB; a full shard needs 20.4 GB).
|
||||
Apple M5 Max: proves on the CPU, slowly.
|
||||
State: idle · nothing assigned · 0 assigned · 0 proven · 0 paid · 0.0000 IGN
|
||||
[Set up] About 20 minutes, once. (only when the prover is not installed)
|
||||
Details ⌄ verifier (word and reason), the program id and the aggregator id with Copy, the segments line
|
||||
```
|
||||
|
||||
The tier sentence per card: NVIDIA with 24 GB or more "proves while it mines"; NVIDIA under 24 GB "cannot prove a
|
||||
full shard (N GB; a full shard needs 20.4 GB)"; Apple silicon "proves on the CPU, slowly"; AMD and other "cannot
|
||||
prove yet (the prover is CUDA only)". The 20.4 GB and the 24 GB rule are the app's own words today
|
||||
(`index.html`, Prove lead); the engine's `proving.available` and `backend` win when they disagree.
|
||||
|
||||
## 7. Settings
|
||||
|
||||
Plain rows, one sentence each, grouped:
|
||||
|
||||
| Group | Rows |
|
||||
|---|---|
|
||||
| Tuning | goal (segmented) · Ember Tune (switch: "Tunes every card for hashes per watt: once after install, then every 7 days, and after a driver or program change.") · Power control (switch: "Lets the app set NVIDIA power and clock limits. Windows asks for administrator rights once. Off, NVIDIA cards are measured only.") · Electricity price (pence per kWh; the £ figures use it) · Hill climb (switch, Ember 2, when `tune_climb` is on the state) |
|
||||
| This machine | Start at login · Name · Allow remote jobs from Igneum ("Signed jobs run here once and report back.") with "History" opening the table and the signing key · Appearance: System / Light / Dark |
|
||||
| Updates | one card: "Igneum Miner 0.3.13 · up to date, checked 13 min ago" · Check · Install now when ready · "Install updates by itself" switch |
|
||||
| Logs | Copy the log · Show the log · the two folders |
|
||||
| Advanced | Vote on finality checkpoints · Trust proof records (devnet, confirm in place, restarts the node) · Open the live devnet page · the machine id |
|
||||
|
||||
The dev fee moved to Earnings (it is money). Identities and the power cap moved to the card.
|
||||
|
||||
## 8. Confirmations in place
|
||||
|
||||
No `confirm()`, no dialogs (the viewer never shows them). A destructive or costly action turns its own row into a
|
||||
question with two buttons:
|
||||
|
||||
| Action | The row says |
|
||||
|---|---|
|
||||
| Quit | `Quit Igneum Miner? Mining stops first, then the node. Quit Cancel` |
|
||||
| Change address | `Pay 0x12…34 from the next block? The miner restarts. Confirm Cancel` |
|
||||
| Show my key | `Show the private key on screen? Anyone who sees it can spend what the address holds. Show Cancel` |
|
||||
| Trust mode | `Include unverified proof records? Devnet only; the node restarts. Turn on Cancel` |
|
||||
| Stop a tune, stop mining, switch a card off, Tune all, the dev fee, identities | no question: each reverts in one tap and the toast says what happened |
|
||||
|
||||
## 9. Type, spacing, colour
|
||||
|
||||
| Token | Value | Use |
|
||||
|---|---|---|
|
||||
| `--t-xs` 11, `--t-sm` 12, `--t-base` 13, `--t-md` 14, `--t-lg` 15, `--t-xl` 17, `--t-num` 24, `--t-hero` 34, `--t-h2` 28, `--t-h1` 44 | px | eight sizes; Unbounded only for the page title, the big button, the hero numbers and the row's rate; IBM Plex Sans for words; IBM Plex Mono for labels, units and small numbers |
|
||||
| `--s-1` 4, `--s-2` 8, `--s-3` 12, `--s-4` 16, `--s-5` 24, `--s-6` 32 | px | one spacing scale; cards pad `--s-5`, rows pad `--s-4`, gaps `--s-3` |
|
||||
| radius | 16 card, 12 row, 10 button, 999 pill | |
|
||||
| dark | obsidian `#0C0C0E` page, graphite `#16161A` card, `#111114` row, lines `#2A2A30` | the brand |
|
||||
| light | `#F4F1EC` (bone) page, `#FFFFFF` card, `#FAF8F5` row, lines `#E2DED8`, ink `#16161A`, ash `#6B6B70` | `prefers-color-scheme: light` and `[data-theme=light]`; ember stays `#F2541B`, molten darkens to `#B8731F` for text on light |
|
||||
| ember | the big button, the one primary per view, the rate, errors | one accent |
|
||||
| molten | live states (mining, synced), the pill | |
|
||||
|
||||
Contrast: every text on its background at 4.5:1 or more (ash on obsidian 5.9:1; ash on bone 5.2:1).
|
||||
|
||||
## 10. Phone width and the narrow window
|
||||
|
||||
| Width | Layout |
|
||||
|---|---|
|
||||
| 1180 px and up | rail 196 px, the hero in one row, the rows with every cell |
|
||||
| 900 to 1180 | rail folds to icons, the hero wraps to two lines |
|
||||
| 720 to 900 | rail 76 px, the row's money and temperature cells go under the name as a second line |
|
||||
| under 720 | the rail becomes a bottom tab bar (Mine, Earnings, Prove, Settings; Logs and Quit under Settings), the hero stacks, every row is two lines (name and state; numbers in a 2 by 2 grid), the big button full width, 16 px gutters, no horizontal scroll |
|
||||
|
||||
## 11. What the engine is asked for (none of it built here)
|
||||
|
||||
| Field or route | Why | Owner |
|
||||
|---|---|---|
|
||||
| `settings.tune_goal`, `settings.power_price_pence`, `settings.tune_period_s`, `POST /api/tune/goal` | the goal, the price, the schedule | ember-tune (exists at e9738dd) |
|
||||
| `tune_step`, `tune_steps`, `tune_eta_s`, `tune_plan`, `tune_curve`, states `tuning` and `held` | the running line, the details table | ember-tune (exists) |
|
||||
| `watts_total` on `mining` and `pounds_per_day` | the tray tip and the menu bar; the UI sums the cards itself | engine, owed |
|
||||
| `price_gbp_per_ign` | £ earned when a market exists | engine, later |
|
||||
| `address.balance_wei` | the balance on Earnings (miner-ui-2 follow-up) | engine, owed |
|
||||
|
||||
Until a field arrives the UI treats it as unset: no goal control change is sent to a build without the route (the
|
||||
toast says "needs the Ember Tune update"), the price is kept in the page's own storage as a fallback, the schedule
|
||||
line says "not tuned yet".
|
||||
|
||||
## 12. Tests
|
||||
|
||||
`view.test.mjs` grows with: the row model for every state word (mining, tuning with step and eta, paused, waiting,
|
||||
restarting with the message, off with the reason, held, faulted, failed, unusable, removed), the money line at a
|
||||
price and without one, the power and temperature cells hidden when not reported, the tune line for never, running,
|
||||
tuned, prior, measured, pinned, stopped and fleet-paused, the next-check weekday, the prove tier sentence per vendor
|
||||
and memory, the node line, the goal consequence, the quit and address confirmations' words. `tune-line.test.mjs`
|
||||
is ember-tune's version (the row word, the button, the strip). `notices.test.mjs` and `update-card.test.mjs` are
|
||||
unchanged and pass.
|
||||
|
||||
## 13. The build (6 October 2026, evening)
|
||||
|
||||
Branch `miner-ui-3`, three UI files rewritten, nothing on the Rust side (the engine is read as it is; the Ember
|
||||
fields are read when present and treated as unset when a build lacks them). Screenshots of the result beside the
|
||||
audit's: `docs/plans/miner-ui-3/n00-*.png` to `n26-*.png` (the mock on port 4317 from this worktree, the rig,
|
||||
the Mac, tuning, tuned with the details open, the node details, earnings with the address ask, prove, settings,
|
||||
the quit ask, paused, syncing, the clock, the update card, the job strip, the logs, 900 px, 390 px, light).
|
||||
|
||||
| File | What |
|
||||
|---|---|
|
||||
| `app/igneum-app/ui/index.html` | four pages behind the rail; the Mine hero (big button, the live total: MH/s, W, £ a day, blocks); the Tuning card (goal, schedule, Power control when it is the fix); the node line with Details; Activity with the strip and eight events; Earnings (money rows, the dev-fee switch, the address, Change address and Show my key as disclosures with their asks in place); Prove (switch, tier list, one counts line, Set up, Details); Settings (Tuning, This machine with the jobs row and History, Appearance, the update card, Logs, Advanced with the trust ask); the quit ask strip; the key sheet with the key first; the welcome with the mark, not the retired coin |
|
||||
| `app/igneum-app/ui/app.css` | dark and light token sets (`prefers-color-scheme` and `[data-theme]`), the row grid (`.gl-main`, `.nums`, `.acts`, `.gl-tune`, `.gl-details`), the totals, the segmented control, the disclosures, the asks, the bottom tab bar under 720 px, the folded rail under 1000 px |
|
||||
| `app/igneum-app/ui/app.js` | `Notices` with ember-tune's `tuneNotice`; `UpdateCard.NAME` is "Igneum Miner" (was "Igneum Ember"); `View` adds `rowWord`, `cells`, `poundsPerDay`, `money`, `effOf`, `wattsTotal`, `noReading`, `tuneWords`, `nextCheck`, `schedule`, `goalWords`, `nodeLine`, `proveTier`, `proveLine`, `eventLine`, `addressAsk`, `trustAsk` and ember-tune's `tuneWord`, `tuneEta`, `tuningCard` and toggle states; `TuneLine` is ember-tune's plus `pounds`; the DOM block renders the four pages, the row with its details (cap slider, identities, pin, telemetry, the tune table, the facts), the asks, the goal (`POST api/tune/goal`, a toast when the build lacks it), the price (the engine's `power_price_pence` when set, else the page's own storage), the appearance; `?tune=running|tuned|measured` for screenshots |
|
||||
| `app/igneum-app/ui/view.test.mjs` | 7 new tests: the row word per state (paused on an on card, held, tuning, starting, failed), money and the cells, the tune line for every state, the next check, the schedule and the goal, the prove tier and line, the node line, the activity line, the asks |
|
||||
| `app/igneum-app/ui/tune-line.test.mjs` | ember-tune's version (e9738dd) as is, so the ember-tune merge carries no test change |
|
||||
| `app/igneum-app/ui/update-card.test.mjs` | one expectation: the card's name |
|
||||
|
||||
`node --test` on the four UI files: 35 pass. The Rust side is untouched, so the app's unit tests are as master's; the
|
||||
0.3.14 shipper runs them on PC 2 as usual (the rule: test suites run on the PCs).
|
||||
|
||||
### The tick list (section 3 of the audit)
|
||||
|
||||
| # | Feature | Where it is now |
|
||||
|---|---|---|
|
||||
| 1 | Setup screens and the key sheet | as before; the address screen's button says Continue until "Use my own address" is picked |
|
||||
| 2 | Start / stop | the big button |
|
||||
| 3 | Per-card switch | the row |
|
||||
| 4 | Rate, temperature, power per card; totals | the row's cells (left out when not reported); the hero's total with W and £ |
|
||||
| 5 | Blocks found, this run, last 10 min, last hour, dev-fee count, chain height | the hero (this run, lifetime), Activity's line, the dev fee on Earnings, the height on the node line |
|
||||
| 6 | Next program countdown and epoch | the node details |
|
||||
| 7 | Blocks strip | Activity, 110 px |
|
||||
| 8 | Activity feed | Activity, eight lines, JSON cut |
|
||||
| 9 | Hot-plug states | the row (the words and the strip as before) |
|
||||
| 10 | Proving switch, setup, counts, segments, verifier, program ids | Prove: the switch, the tier list, one line, Set up, Details |
|
||||
| 11 | Address with Copy, source, blocks | Earnings |
|
||||
| 12 | Show / hide the key, the file path | Earnings, "Show my key" with the ask |
|
||||
| 13 | Change the address | Earnings, "Change address" with the ask |
|
||||
| 14 | Open the wallet | Earnings |
|
||||
| 15 | Node state, height, peers, version, chain numbers, digest, switches, finality, sync ETA | the node line and its Details |
|
||||
| 16 | Clock card with Sync clock | Mine, above the hero (and the strip on the setup screens) |
|
||||
| 17 | Version, update note, Check, Install now, auto-update | Settings, one card |
|
||||
| 18 | The update card and the strip | unchanged |
|
||||
| 19 | Remote jobs: allow, Check now, note, history, signing key | Settings, This machine: the switch, the line, History |
|
||||
| 20 | Power cap slider, applied / Retry, telemetry, efficiency | the row's details; MH/W on the row |
|
||||
| 21 | Identities | the row's details |
|
||||
| 22 | Ember Tune switch, Tune / Stop / Unpin, the tune line | Settings (switch); the row (Tune, Stop, the line); Unpin in the details; Tune all on the card list |
|
||||
| 23 | Power control | Settings, and Mine's Tuning card when an NVIDIA card has it off |
|
||||
| 24 | Start at login, vote, dev fee, name, trust, live page | Settings (vote and trust under Advanced), the dev fee on Earnings |
|
||||
| 25 | Copy the log, show the log, the folders | Settings, Logs |
|
||||
| 26 | The log drawer | unchanged |
|
||||
| 27 | Quit | the rail, with the ask strip instead of a dialog |
|
||||
| 28 | The pill, the rail foot | the pill (with "tuning"); the foot has the name, the address, the uptime |
|
||||
| 29 | Keyboard | the rail's arrows (left and right too), Escape on the card and the quit ask, Enter on the price and the time box |
|
||||
| 30 | The URL switches | as before, plus `?tune=` |
|
||||
| new | Goal, Tune all, the price, £ a day, MH/W on the row, the tune schedule, the prove tier, the node line, light mode, appearance, the bottom tab bar | this build |
|
||||
|
||||
### Owed
|
||||
|
||||
| What | Who |
|
||||
|---|---|
|
||||
| `watts_total` and `pounds_per_day` on the state for the tray tip and the menu bar; `address.balance_wei`; `price_gbp_per_ign` | the engine (section 11) |
|
||||
| The wallet's copy of the update card still says "Igneum Ember" (`app/igneum-wallet/ui/update-card.js`) | the wallet |
|
||||
| A Windows and a PC screenshot of the row with a real NVIDIA card (the telemetry cells, the cap slider, Retry) | the 0.3.14 cut, first thing to look at |
|
||||
| The row's state word while tuning carries the meta after it ("tuning: step 7 of 9 · measuring 441 W · 32 GB · ...") and is cut on a narrow row | a later pass: drop the meta while tuning |
|
||||
| The merge with `ember-tune`: `app.js` takes this branch's file (it already carries ember-tune's UI words and test); the engine files merge by themselves | the 0.3.14 shipper |
|
||||
BIN
docs/plans/miner-ui-3/00-welcome.png
Normal file
|
After Width: | Height: | Size: 206 KiB |
BIN
docs/plans/miner-ui-3/01-setup-gpu.png
Normal file
|
After Width: | Height: | Size: 89 KiB |
BIN
docs/plans/miner-ui-3/02-setup-address.png
Normal file
|
After Width: | Height: | Size: 146 KiB |
BIN
docs/plans/miner-ui-3/03-setup-key.png
Normal file
|
After Width: | Height: | Size: 177 KiB |
BIN
docs/plans/miner-ui-3/04-mine-live.png
Normal file
|
After Width: | Height: | Size: 241 KiB |
BIN
docs/plans/miner-ui-3/05-prove-live.png
Normal file
|
After Width: | Height: | Size: 307 KiB |
BIN
docs/plans/miner-ui-3/06-rewards-live.png
Normal file
|
After Width: | Height: | Size: 234 KiB |
BIN
docs/plans/miner-ui-3/07-node-live.png
Normal file
|
After Width: | Height: | Size: 254 KiB |
BIN
docs/plans/miner-ui-3/08-updates-live.png
Normal file
|
After Width: | Height: | Size: 355 KiB |
BIN
docs/plans/miner-ui-3/09-settings-live.png
Normal file
|
After Width: | Height: | Size: 338 KiB |
BIN
docs/plans/miner-ui-3/10-logs-drawer-live.png
Normal file
|
After Width: | Height: | Size: 408 KiB |
BIN
docs/plans/miner-ui-3/11-update-card-ready.png
Normal file
|
After Width: | Height: | Size: 375 KiB |
BIN
docs/plans/miner-ui-3/12-strip-update-downloading.png
Normal file
|
After Width: | Height: | Size: 221 KiB |
BIN
docs/plans/miner-ui-3/13-strip-job-running.png
Normal file
|
After Width: | Height: | Size: 215 KiB |
BIN
docs/plans/miner-ui-3/14-strip-job-failed.png
Normal file
|
After Width: | Height: | Size: 233 KiB |
BIN
docs/plans/miner-ui-3/15-mine-nvidia-rig.png
Normal file
|
After Width: | Height: | Size: 275 KiB |
BIN
docs/plans/miner-ui-3/16-settings-nvidia-rig.png
Normal file
|
After Width: | Height: | Size: 322 KiB |
BIN
docs/plans/miner-ui-3/17-mine-syncing.png
Normal file
|
After Width: | Height: | Size: 228 KiB |
BIN
docs/plans/miner-ui-3/18-node-syncing.png
Normal file
|
After Width: | Height: | Size: 256 KiB |
BIN
docs/plans/miner-ui-3/19-mine-nodedown.png
Normal file
|
After Width: | Height: | Size: 230 KiB |
BIN
docs/plans/miner-ui-3/20-mine-nocards.png
Normal file
|
After Width: | Height: | Size: 221 KiB |
BIN
docs/plans/miner-ui-3/21-node-clock.png
Normal file
|
After Width: | Height: | Size: 283 KiB |
BIN
docs/plans/miner-ui-3/22-narrow-900-mine.png
Normal file
|
After Width: | Height: | Size: 163 KiB |
BIN
docs/plans/miner-ui-3/23-narrow-900-settings.png
Normal file
|
After Width: | Height: | Size: 199 KiB |
BIN
docs/plans/miner-ui-3/24-phone-mine.png
Normal file
|
After Width: | Height: | Size: 158 KiB |
BIN
docs/plans/miner-ui-3/25-phone-settings.png
Normal file
|
After Width: | Height: | Size: 148 KiB |
BIN
docs/plans/miner-ui-3/26-light-scheme-mine.png
Normal file
|
After Width: | Height: | Size: 242 KiB |
BIN
docs/plans/miner-ui-3/n00-welcome.png
Normal file
|
After Width: | Height: | Size: 171 KiB |
BIN
docs/plans/miner-ui-3/n01-setup-gpu.png
Normal file
|
After Width: | Height: | Size: 74 KiB |
BIN
docs/plans/miner-ui-3/n02-setup-address.png
Normal file
|
After Width: | Height: | Size: 122 KiB |
BIN
docs/plans/miner-ui-3/n03-setup-key.png
Normal file
|
After Width: | Height: | Size: 155 KiB |
BIN
docs/plans/miner-ui-3/n04-mine-mac.png
Normal file
|
After Width: | Height: | Size: 224 KiB |
BIN
docs/plans/miner-ui-3/n05-mine-rig.png
Normal file
|
After Width: | Height: | Size: 292 KiB |
BIN
docs/plans/miner-ui-3/n06-mine-rig-tuning.png
Normal file
|
After Width: | Height: | Size: 268 KiB |
BIN
docs/plans/miner-ui-3/n07-mine-rig-tuned-open.png
Normal file
|
After Width: | Height: | Size: 311 KiB |
BIN
docs/plans/miner-ui-3/n08-mine-node-open.png
Normal file
|
After Width: | Height: | Size: 263 KiB |
BIN
docs/plans/miner-ui-3/n09-earnings.png
Normal file
|
After Width: | Height: | Size: 188 KiB |
BIN
docs/plans/miner-ui-3/n10-earnings-change.png
Normal file
|
After Width: | Height: | Size: 228 KiB |
BIN
docs/plans/miner-ui-3/n11-prove.png
Normal file
|
After Width: | Height: | Size: 220 KiB |
BIN
docs/plans/miner-ui-3/n12-settings.png
Normal file
|
After Width: | Height: | Size: 225 KiB |
BIN
docs/plans/miner-ui-3/n13-settings-lower.png
Normal file
|
After Width: | Height: | Size: 228 KiB |
BIN
docs/plans/miner-ui-3/n14-quit-ask.png
Normal file
|
After Width: | Height: | Size: 240 KiB |
BIN
docs/plans/miner-ui-3/n15-mine-paused.png
Normal file
|
After Width: | Height: | Size: 216 KiB |
BIN
docs/plans/miner-ui-3/n16-mine-syncing.png
Normal file
|
After Width: | Height: | Size: 222 KiB |
BIN
docs/plans/miner-ui-3/n17-mine-clock.png
Normal file
|
After Width: | Height: | Size: 234 KiB |
BIN
docs/plans/miner-ui-3/n18-update-card.png
Normal file
|
After Width: | Height: | Size: 346 KiB |
BIN
docs/plans/miner-ui-3/n19-strip-job.png
Normal file
|
After Width: | Height: | Size: 231 KiB |
BIN
docs/plans/miner-ui-3/n20-logs.png
Normal file
|
After Width: | Height: | Size: 398 KiB |
BIN
docs/plans/miner-ui-3/n21-narrow-900-mine.png
Normal file
|
After Width: | Height: | Size: 143 KiB |
BIN
docs/plans/miner-ui-3/n22-phone-mine.png
Normal file
|
After Width: | Height: | Size: 125 KiB |
BIN
docs/plans/miner-ui-3/n23-phone-settings.png
Normal file
|
After Width: | Height: | Size: 171 KiB |
BIN
docs/plans/miner-ui-3/n24-light-mine.png
Normal file
|
After Width: | Height: | Size: 298 KiB |
BIN
docs/plans/miner-ui-3/n25-light-settings.png
Normal file
|
After Width: | Height: | Size: 231 KiB |
BIN
docs/plans/miner-ui-3/n26-mine-measured-mac.png
Normal file
|
After Width: | Height: | Size: 266 KiB |