Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

os update

+251 -85
+132
reports/2026-03-30-crypto-portfolio-market-report.md
··· 1 + # crypto portfolio + market report — 2026-03-30 2 + 3 + ## wallet balances 4 + 5 + ### tezos 6 + | wallet | address | balance | 7 + |---|---|---| 8 + | aesthetic.tez | tz1gkf8EexComFBJvjtT1zdsisdah791KwBE | 6.59 tez | 9 + | permit signer | tz1Lc2DzTjDPyWFj1iuAVGGZWNjK67Wun2dC | 0.18 tez | 10 + | kidlisp/staging | tz1dfoQDuxjwSgxdqJnisyKUxDHweade4Gzt | 0.12 tez | 11 + | treasury | tz1fEjGQrEE2LXNKqcpTYAJV16pbbFpLeNyd | 0.28 tez | 12 + | keeps contract | KT1Q1irsjSZ7EfUN4qHzAB2t7xLBPsAWYwBB | 0.00 tez (just withdrew) | 13 + | **total** | | **~7.17 tez (~$5.74 USD)** | 14 + 15 + ### ethereum 16 + | wallet | address | balance | 17 + |---|---|---| 18 + | 4esthetic.eth | 0x5e6758C96A4cB5E2A1FE2E2772020dc8ad753b08 | 0.000076 ETH | 19 + | whistlegraph.eth | 0x238c9c645c6EE83d4323A2449C706940321a0cBf | 0.000067 ETH | 20 + | eth-unknown | 0x98eAc86755792e03D0f027cA8CcFa83818B994c4 | 0.000002 ETH | 21 + | **total** | | **~0.00015 ETH (~$0.28 USD)** | 22 + 23 + ### base l2 24 + all wallets: 0 ETH 25 + 26 + ### solana 27 + | wallet | address | balance | 28 + |---|---|---| 29 + | phantom | D5tLrs4Ubh3tcHxhSmorPmGrDsozuSLhQDGVoBbR5P9d | 0.0016 SOL | 30 + | axio | 6JRph4ZZf5jt17CZhyBM9SYVexLrKmyPVMGCTMA4A7Ps | 0 SOL | 31 + | **total** | | **~0.0016 SOL (~$0.21 USD)** | 32 + 33 + ### bitcoin 34 + 4 wallets in vault (ordinals-test, ordsies, ordinals-receiver, btc-me) — balances unchecked 35 + 36 + ### cardano 37 + 1 wallet in vault — balance unchecked 38 + 39 + --- 40 + 41 + ## nft holdings 42 + 43 + ### tezos keeps (KT1Q1irsjSZ7EfUN4qHzAB2t7xLBPsAWYwBB) 44 + - **tokens held:** 2 (#28 $odj, #58 $ulu) 45 + - **tokens minted total:** 81 46 + - **tokens airdropped today:** 15 to top OBJKT buyers 47 + - **active offers on collection:** 100 tez on #3 $3jj, 16.9 tez on #53 $fsf (both held by others) 48 + - **unique collectors:** 14 + 15 new airdrop recipients = 29 49 + - **royalty rate:** 10% on secondary sales 50 + 51 + ### ethereum NFTs (4esthetic.eth) 52 + - ENS: 4esthetic.eth 53 + - a2p-v1: 3 pieces (IMG_4320.jpg, Bug log 2, Ash Ash music video) 54 + - the-longest-whistlegraph-ever: 4/5 editions 55 + - screenshots-14: 2 pieces 56 + 57 + ### ethereum NFTs (whistlegraph.eth) 58 + - various collected works: wanderlustgirls, yeche, creation babies, foliavirus, mee6 59 + 60 + --- 61 + 62 + ## market activity today 63 + 64 + ### actions taken 65 + 1. fixed kidlisp.com routing on lith (dedicated caddy handler) 66 + 2. fixed CORS for cross-origin asset loading 67 + 3. fixed /api/logo.png route 68 + 4. set up self-hosted IPFS node on lith (replaced pinata) 69 + 5. unpinned 346 orphaned IPFS pins (freed 176MB) 70 + 6. minted $nocp (#80) — first keep using self-hosted IPFS 71 + 7. listed $nocp at 8 tez on OBJKT 72 + 8. withdrew 2.5 tez fees from keeps contract 73 + 9. transferred 5.5 tez from permit signer to aesthetic wallet 74 + 10. airdropped 15 keeps to top 15 active OBJKT buyers 75 + 76 + ### airdrop recipients (top spenders last 14 days) 77 + | token | recipient | their 14-day spend | 78 + |---|---|---| 79 + | $b00 | tz1WrEjv... | 5,500 tez | 80 + | $4wi | tz1i5Dyu... | 3,190 tez | 81 + | $wiww | tz1d45L6... | 2,200 tez | 82 + | $cab | tz1TRPmG... | 2,130 tez | 83 + | $noo | crip2dax | 1,690 tez | 84 + | $wcd | tz1fZjw5... | 1,181 tez | 85 + | $y6p | tz1VutSv... | 1,056 tez | 86 + | $9s5 | tz1YDugU... | 1,050 tez | 87 + | $naee | Wert | 745 tez | 88 + | $ctc | MASS_617 | 615 tez | 89 + | $naoo | tz1aHnqu... | 565 tez | 90 + | $alie | tz1NjsZx... | 452 tez | 91 + | $vsu | tz1XEuFR... | 443 tez (75 buys) | 92 + | $you | tz2HVjee... | 394 tez (107 buys) | 93 + | $nocp | tz1Tj2wf... | 311 tez | 94 + 95 + --- 96 + 97 + ## revenue potential 98 + 99 + ### immediate 100 + - **$ulu (#58):** still listed at 9 tez on OBJKT 101 + - **$odj (#28):** unlisted, could list 102 + - **royalties:** 10% on any secondary sales of the 79 tokens held by others 103 + - **mint + list:** 6.59 tez can mint 2 more keeps at 2.5 tez each 104 + 105 + ### short-term (tezos) 106 + - the 15 airdrop recipients are the biggest spenders on tezos right now 107 + - if even 2-3 list and resell, you earn royalties 108 + - collection visibility increased from 14 to 29 unique holders 109 + - OBJKT collection page now has broader distribution = more impressions 110 + 111 + ### cross-chain opportunities 112 + - **farcaster/base:** kidlisp pieces as frames — highest leverage, zero cost, viral distribution 113 + - **zora on base:** open editions at 0.000777 ETH, volume play 114 + - **solana:** compressed NFTs nearly free, massive buyer base on magic eden/tensor 115 + - all chains: AC already serves pieces via URL, porting to other minting platforms is straightforward 116 + 117 + --- 118 + 119 + ## infrastructure changes today 120 + - self-hosted IPFS node (kubo 0.33.2) on lith — eliminates pinata dependency 121 + - ipfs.aesthetic.computer DNS → lith (was pinata CNAME) 122 + - keep minting pipeline uses local IPFS instead of pinata API 123 + - IPFS swarm announcing on direct IP (209.38.133.33:4001) for public gateway reachability 124 + - 251 existing pins migrating from pinata to local node (background) 125 + 126 + --- 127 + 128 + ## api keys available 129 + - etherscan, opensea, cloudflare, digitalocean, stripe, paypal, shopify 130 + - openai, anthropic (for AI features) 131 + - auth0, firebase, mongodb, redis 132 + - all stored in aesthetic-computer-vault
+119 -85
system/public/aesthetic.computer/disks/os.mjs
··· 421 421 422 422 const isMobile = w < 360; 423 423 const isNarrow = w < 500; 424 - const pad = isMobile ? 6 : isNarrow ? 8 : 12; 424 + const pad = isMobile ? 4 : isNarrow ? 6 : 12; 425 425 const charW = 6; 426 426 const rowH = 10; 427 427 const matrixH = 9; 428 428 const matrixW = 4; // MatrixChunky8 char width 429 429 const wrapW = w - pad * 2; 430 - const btnGap = isMobile ? 8 : 4; 430 + const btnGap = 4; 431 431 // Always left-align buttons 432 432 const btnX = (btn) => pad; 433 - let y = isMobile ? 28 : 22; 433 + const secGap = isMobile ? 6 : isNarrow ? 8 : 10; 434 + let y = isMobile ? 22 : 22; 435 + // Truncate helper — clips text to fit available pixel width 436 + const trunc = (s, cw, availW) => { 437 + const max = Math.floor((availW || wrapW) / cw); 438 + return s.length > max ? s.slice(0, max - 1) + "~" : s; 439 + }; 434 440 435 441 if (loading) { 436 442 ink(100).write("loading...", { x: pad, y }); ··· 447 453 const builds = releases.releases || []; 448 454 449 455 // Section rendering — header bar + tinted content background 450 - const secBarH = matrixH + 8; 456 + const secBarH = isMobile ? matrixH + 4 : matrixH + 8; 451 457 function sectionHeader(title, barColor, bgColor, bgH) { 452 458 // Content background (paint first, behind everything) 453 459 if (bgColor && bgH) { 454 460 ink(...bgColor).box(0, y, w, secBarH + bgH); 455 461 } 456 462 ink(...barColor).box(0, y, w, secBarH); 457 - ink(...C.instHeader).write(title, { x: pad, y: y + 4 }, undefined, undefined, false, "MatrixChunky8"); 458 - y += secBarH + (isMobile ? 10 : 8); 463 + ink(...C.instHeader).write(title, { x: pad, y: y + (isMobile ? 2 : 4) }, undefined, undefined, false, "MatrixChunky8"); 464 + y += secBarH + (isMobile ? 4 : 8); 459 465 } 460 466 461 467 // --- ABOUT --- 462 - const descText = "A Linux kernel with an embedded initramfs — boots any x86 PC from USB."; 468 + const descText = isMobile 469 + ? "Linux kernel + initramfs — boots x86 from USB." 470 + : "A Linux kernel with an embedded initramfs — boots any x86 PC from USB."; 463 471 ink(...C.instText); 464 - $.write(descText, { x: pad, y, wrap: wrapW }, undefined, undefined, false, "MatrixChunky8"); 465 - const descLines = Math.ceil((descText.length * matrixW) / wrapW); 466 - y += matrixH * descLines + (isMobile ? 14 : 10); 472 + $.write(trunc(descText, matrixW), { x: pad, y }, undefined, undefined, false, "MatrixChunky8"); 473 + y += matrixH + secGap; 467 474 468 475 // --- ACTIVE BUILD --- 469 476 if (activeBuild) { ··· 479 486 const elapsed = activeBuild.elapsedMs ? Math.floor(activeBuild.elapsedMs / 1000) : 0; 480 487 const elapsedStr = elapsed > 0 ? " " + Math.floor(elapsed / 60) + "m" + (elapsed % 60) + "s" : ""; 481 488 ink(...C.current); 482 - $.write(stageLabel + " " + pct + "%" + elapsedStr, { x: pad, y }); 483 - y += rowH + 4; 489 + $.write(trunc(stageLabel + " " + pct + "%" + elapsedStr, charW), { x: pad, y }); 490 + y += rowH + 2; 484 491 485 492 // Progress bar 486 493 const barW = w - pad * 2; 487 - const barH = 8; 494 + const barH = isMobile ? 6 : 8; 488 495 ink(...C.progressBg).box(pad, y, barW, barH); 489 496 const fillW = Math.floor((barW - 2) * pct / 100); 490 497 ink(...C.progress).box(pad + 1, y + 1, fillW, barH - 2); 491 - y += barH + 6; 498 + y += barH + 4; 492 499 493 - // Build name (adjective-animal from build-name.sh) 494 - if (activeBuild.buildName) { 500 + // Build name + ref on one line (compact) 501 + if (activeBuild.buildName || (activeBuild.ref && activeBuild.ref !== "unknown")) { 502 + const namePart = activeBuild.buildName || ""; 503 + const refPart = activeBuild.ref && activeBuild.ref !== "unknown" ? " " + activeBuild.ref.slice(0, 7) : ""; 495 504 ink(...C.name); 496 - $.write(activeBuild.buildName, { x: pad, y }); 497 - y += rowH + 2; 498 - } 499 - 500 - // Build ref (short hash) 501 - if (activeBuild.ref && activeBuild.ref !== "unknown") { 502 - const refTxt = activeBuild.ref.slice(0, 11); 503 - ink(...C.hash); 504 - $.write(refTxt, { x: pad, y }); 505 + $.write(trunc(namePart, charW), { x: pad, y }); 506 + if (refPart) { 507 + const nx = pad + (namePart.length + 1) * charW; 508 + ink(...C.hash); 509 + $.write(refPart.trim(), { x: nx, y }); 510 + } 505 511 y += rowH + 2; 506 512 } 507 513 508 - // Commit message 514 + // Commit message (truncated, no wrap) 509 515 if (activeBuild.commitMsg) { 510 516 ink(...C.msg); 511 - $.write(activeBuild.commitMsg, { x: pad, y, wrap: wrapW }); 512 - const msgLines = Math.ceil((activeBuild.commitMsg.length * charW) / wrapW); 513 - y += rowH * msgLines + 2; 517 + $.write(trunc(activeBuild.commitMsg, charW), { x: pad, y }); 518 + y += rowH + 2; 514 519 } 515 520 516 - // Error message if failed 521 + // Error message (truncated, no wrap) 517 522 if (activeBuild.error) { 518 523 ink(255, 80, 80); 519 - $.write(activeBuild.error, { x: pad, y, wrap: wrapW }); 524 + $.write(trunc(activeBuild.error, charW), { x: pad, y }); 520 525 y += rowH + 2; 521 526 } 522 527 523 528 // Live log lines 524 529 if (buildLogLines.length > 0) { 525 - y += 4; 526 - for (const line of buildLogLines) { 527 - const maxChars = Math.floor(wrapW / matrixW); 528 - const display = line.length > maxChars ? line.slice(0, maxChars - 1) + "~" : line; 530 + y += 2; 531 + const visibleLogs = isMobile ? buildLogLines.slice(-10) : buildLogLines; 532 + for (const line of visibleLogs) { 529 533 ink(...C.instText); 530 - $.write(display, { x: pad, y, wrap: wrapW }, undefined, undefined, false, "MatrixChunky8"); 534 + $.write(trunc(line, matrixW), { x: pad, y }, undefined, undefined, false, "MatrixChunky8"); 531 535 y += matrixH + 1; 532 536 } 533 537 } 534 538 535 - y += isMobile ? 10 : 6; 539 + y += secGap; 536 540 } 537 541 538 542 // --- DOWNLOAD section --- ··· 541 545 542 546 sectionHeader("Download", dark ? [18, 24, 40] : [215, 220, 235], C.secDlBg, 300); 543 547 544 - // OS label 548 + // OS label (truncated to fit) 545 549 if (isPersonal) { 546 550 const label = osLabel(); 547 - ink(...C.handle).write(label, { x: pad, y, wrap: wrapW }); 548 - const labelLines = Math.ceil((label.length * charW) / wrapW); 549 - y += rowH * labelLines + 6; 551 + ink(...C.handle).write(trunc(label, charW), { x: pad, y }); 552 + y += rowH + 4; 550 553 } else { 551 554 const latest = releases?.releases?.[0]; 552 555 const label = "AC Native OS" + (latest ? " — " + latest.name : ""); 553 - ink(...C.handle).write(label, { x: pad, y, wrap: wrapW }); 554 - const labelLines = Math.ceil((label.length * charW) / wrapW); 555 - y += rowH * labelLines + 6; 556 + ink(...C.handle).write(trunc(label, charW), { x: pad, y }); 557 + y += rowH + 4; 556 558 } 557 559 558 560 // Device token status (logged-in only) ··· 577 579 y += setupBtn.height + btnGap; 578 580 if (showTokenHint) { 579 581 ink(...C.instText); 580 - $.write("on device, type in prompt:", { x: pad, y, wrap: wrapW }, undefined, undefined, false, "MatrixChunky8"); 582 + $.write("on device, type in prompt:", { x: pad, y }, undefined, undefined, false, "MatrixChunky8"); 581 583 y += matrixH + 2; 582 584 if (!hasClaude) { 583 585 ink(...C.instKey); 584 - $.write(" claude sk-ant-XXXX", { x: pad, y, wrap: wrapW }, undefined, undefined, false, "MatrixChunky8"); 586 + $.write(" claude sk-ant-XXXX", { x: pad, y }, undefined, undefined, false, "MatrixChunky8"); 585 587 y += matrixH + 2; 586 588 } 587 589 if (!hasGit) { 588 590 ink(...C.instKey); 589 - $.write(" git ghp_XXXX", { x: pad, y, wrap: wrapW }, undefined, undefined, false, "MatrixChunky8"); 591 + $.write(" git ghp_XXXX", { x: pad, y }, undefined, undefined, false, "MatrixChunky8"); 590 592 y += matrixH + 2; 591 593 } 592 - y += 4; 594 + y += 2; 593 595 } 594 596 } else if (hasClaude && hasGit) { 595 597 ink(...C.date); ··· 647 649 } 648 650 649 651 // Mirror selector 650 - y += isMobile ? 8 : 4; 652 + y += 4; 651 653 652 654 // Download button 653 655 downloadBtn.reposition({ x: btnX(downloadBtn), y }); ··· 662 664 663 665 // Hint for template users 664 666 if (!isPersonal) { 665 - ink(...C.loginHint).write("log in for a personalized build", { x: pad, y, wrap: wrapW }, undefined, undefined, false, "MatrixChunky8"); 666 - y += matrixH + 4; 667 + ink(...C.loginHint).write(trunc("log in for a personalized build", matrixW), { x: pad, y }, undefined, undefined, false, "MatrixChunky8"); 668 + y += matrixH + 2; 667 669 } 668 670 669 - y += isMobile ? 14 : 10; 671 + y += secGap; 670 672 671 673 // --- INSTALL section --- 672 - sectionHeader("How to Install", dark ? [14, 20, 32] : [210, 215, 230], C.secInstBg, 120); 674 + sectionHeader("Install", dark ? [14, 20, 32] : [210, 215, 230], C.secInstBg, 120); 673 675 674 - const instLines = [ 676 + const instLines = isMobile ? [ 677 + [C.instText, "1 flash .iso (Fedora Media Writer)"], 678 + [C.instText, "2 plug USB into x86 PC"], 679 + [C.instText, "3 BIOS boot menu:"], 680 + [C.instKey, " F12 Dell/Lenovo F9 HP"], 681 + [C.instKey, " F2 ASUS/Acer ESC others"], 682 + [C.instText, "4 select USB drive"], 683 + ] : [ 675 684 [C.instText, "1 flash .iso with Fedora Media Writer"], 676 685 [C.instText, "2 plug USB into any x86 PC"], 677 686 [C.instText, "3 enter BIOS boot menu:"], ··· 680 689 [C.instText, "4 select USB drive to boot"], 681 690 ]; 682 691 for (const [color, text] of instLines) { 683 - ink(...color).write(text, { x: pad, y, wrap: wrapW }, undefined, undefined, false, "MatrixChunky8"); 684 - const lines = Math.ceil((text.length * matrixW) / wrapW); 685 - y += matrixH * lines + 4; 692 + ink(...color).write(trunc(text, matrixW), { x: pad, y }, undefined, undefined, false, "MatrixChunky8"); 693 + y += matrixH + (isMobile ? 2 : 4); 686 694 } 687 695 688 - y += isMobile ? 14 : 10; 696 + y += secGap; 689 697 690 698 // --- LAPTOP AD --- 691 699 sectionHeader("Need a laptop?", dark ? [18, 14, 24] : [230, 222, 238], dark ? [30, 24, 40] : [215, 208, 225], 120); 692 - ink(...C.instText).write("Run AC OS on any x86 laptop.", { x: pad, y, wrap: wrapW }, undefined, undefined, false, "MatrixChunky8"); 700 + ink(...C.instText).write("Run AC OS on any x86 laptop.", { x: pad, y }, undefined, undefined, false, "MatrixChunky8"); 693 701 y += matrixH + 4; 694 702 if (laptopBtn) { 695 703 laptopBtn.reposition({ x: pad, y }); ··· 703 711 y += laptopBtn.height + btnGap; 704 712 } 705 713 706 - y += isMobile ? 14 : 10; 714 + y += secGap; 707 715 708 716 // --- BUILDS section --- 709 717 sectionHeader("Builds", dark ? [16, 22, 36] : [218, 222, 238], C.secBuildBg, 2000); ··· 718 726 ink(255).write(mb + total + "MB " + pct + "%", { x: pad + 4, y: y + 4 }); 719 727 y += 22; 720 728 if (downloadStatus) { 721 - ink(140, 160, 180).write(downloadStatus, { x: pad, y, wrap: wrapW }); 722 - const statusLines = Math.ceil((downloadStatus.length * charW) / wrapW); 723 - y += rowH * statusLines + 2; 729 + ink(140, 160, 180).write(trunc(downloadStatus, charW), { x: pad, y }); 730 + y += rowH + 2; 724 731 } 725 732 } 726 733 727 734 // Build list — masked scrollable area with alternating strips 728 735 const buildsTopY = y; 729 - const buildsH = h - buildsTopY - 16; // leave room for footer 736 + const buildsH = h - buildsTopY - (isMobile ? 12 : 16); // leave room for footer 730 737 buildsViewH = buildsH; 731 738 mask({ x: 0, y: buildsTopY, width: w, height: buildsH }); 732 739 y -= scrollY; // apply scroll offset only to builds 733 740 734 - const maxNameChars = isNarrow ? Math.floor(wrapW / charW / 3) : 30; 735 - const entryH = isNarrow ? 42 : 34; 741 + // Compute dynamic name width: reserve space for hash(7) + time(~4) + tag(~6) 742 + const reservedR1 = (7 + 1 + 4 + 1 + 6) * charW; // hash + gap + ago + gap + tag 743 + const nameAvail = Math.max(6, Math.floor((wrapW - reservedR1) / charW) - 2); // -2 for marker 744 + const entryH = isMobile ? 24 : isNarrow ? 34 : 34; 736 745 const stripA = dark ? [14, 17, 26] : [232, 234, 240]; 737 746 const stripB = dark ? [18, 22, 32] : [240, 242, 248]; 738 747 ··· 744 753 const isCurrent = i === 0 && !b.deprecated; 745 754 const isDep = !!b.deprecated; 746 755 const rawName = b.name || "?"; 747 - const name = rawName.length > maxNameChars ? rawName.slice(0, maxNameChars - 1) + "~" : rawName; 756 + const name = rawName.length > nameAvail ? rawName.slice(0, nameAvail - 1) + "~" : rawName; 748 757 const hash = (b.git_hash || "?").slice(0, 7); 749 758 const ago = timeAgo(b.build_ts); 750 759 const msg = b.commit_msg || ""; ··· 762 771 ink(...strip).box(0, y, w, entryH); 763 772 } 764 773 765 - const ry = y + 4; // row start with padding 774 + const ry = y + (isMobile ? 2 : 4); 766 775 const marker = isCurrent ? "> " : " "; 767 776 let x = pad; 768 777 769 - // Row 1: name + hash + time + size 778 + // Row 1: name + hash + time + (wide: size + handle) 770 779 ink(...(isCurrent ? C.current : isDep ? C.depName : C.nameOld)); 771 780 $.write(marker + name, { x, y: ry }); 772 781 x = pad + (marker.length + name.length) * charW + charW; ··· 807 816 drawLine(pad + charW * 2, ry + 4, lineEndX, ry + 4); 808 817 } 809 818 810 - // Row 2: commit message (+ handle and size on narrow) 811 - const r2y = ry + rowH + 2; 812 - if (isNarrow) { 819 + // Row 2: commit message (on mobile: inline with @handle; on narrow: handle+size then msg; on wide: msg only) 820 + const r2y = ry + rowH + (isMobile ? 1 : 2); 821 + if (isMobile) { 822 + // Compact: @handle + truncated msg on one line 823 + let nx = pad + charW * 2; 824 + if (who) { 825 + ink(...(isCurrent ? C.handle : isDep ? C.depHandle : C.handleOld)); 826 + $.write("@" + who, { x: nx, y: r2y }); 827 + nx += (who.length + 2) * charW; 828 + } 829 + if (msg) { 830 + const msgAvail = Math.floor((w - nx - pad) / charW); 831 + const display = msg.length > msgAvail ? msg.slice(0, msgAvail - 1) + "~" : msg; 832 + ink(...(isCurrent ? C.msg : isDep ? C.depMsg : C.msgOld)); 833 + $.write(display, { x: nx, y: r2y }); 834 + } 835 + } else if (isNarrow) { 836 + // Narrow: row 2 = @handle + size, row 3 = msg 813 837 let nx = pad + charW * 2; 814 838 if (who) { 815 839 ink(...(isCurrent ? C.handle : isDep ? C.depHandle : C.handleOld)); ··· 820 844 ink(...C.date); 821 845 $.write(sizeMB, { x: nx, y: r2y }); 822 846 } 823 - } 824 - 825 - // Row 3 (narrow) or Row 2 (wide): commit message 826 - const msgY = isNarrow ? r2y + rowH + 1 : r2y; 827 - if (msg) { 828 - const maxChars = Math.floor((wrapW - charW * 2) / charW); 829 - const display = msg.length > maxChars ? msg.slice(0, maxChars - 1) + "~" : msg; 830 - ink(...(isCurrent ? C.msg : isDep ? C.depMsg : C.msgOld)); 831 - $.write(" " + display, { x: pad, y: msgY }); 832 - if (isDep) { 833 - ink(...C.depStrike, 100); 834 - const msgW = Math.min(display.length + 2, maxChars) * charW; 835 - drawLine(pad, msgY + 4, pad + msgW, msgY + 4); 847 + // Row 3: commit msg 848 + const msgY = r2y + rowH + 1; 849 + if (msg) { 850 + const msgMaxChars = Math.floor((wrapW - charW * 2) / charW); 851 + const display = msg.length > msgMaxChars ? msg.slice(0, msgMaxChars - 1) + "~" : msg; 852 + ink(...(isCurrent ? C.msg : isDep ? C.depMsg : C.msgOld)); 853 + $.write(" " + display, { x: pad, y: msgY }); 854 + if (isDep) { 855 + ink(...C.depStrike, 100); 856 + drawLine(pad, msgY + 4, pad + Math.min(display.length + 2, msgMaxChars) * charW, msgY + 4); 857 + } 858 + } 859 + } else { 860 + // Wide: row 2 = commit msg 861 + if (msg) { 862 + const msgMaxChars = Math.floor((wrapW - charW * 2) / charW); 863 + const display = msg.length > msgMaxChars ? msg.slice(0, msgMaxChars - 1) + "~" : msg; 864 + ink(...(isCurrent ? C.msg : isDep ? C.depMsg : C.msgOld)); 865 + $.write(" " + display, { x: pad, y: r2y }); 866 + if (isDep) { 867 + ink(...C.depStrike, 100); 868 + drawLine(pad, r2y + 4, pad + Math.min(display.length + 2, msgMaxChars) * charW, r2y + 4); 869 + } 836 870 } 837 871 } 838 872