From 3533e95adcbe2304f795ee53d0ca466307e6580b Mon Sep 17 00:00:00 2001 From: Carlos Mora Date: Sun, 20 Sep 2026 07:09:55 -0500 Subject: [PATCH 1/2] fix(startup): degrade logo gracefully on narrow terminals and fast-forward on animation timeout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Render fallback branding '✿ Gentle Shell ✿' when terminal width < 122 columns and text logo is enabled, preventing the brand from vanishing on narrow sessions. - Fast-forward animation tick to complete static artwork (Number.MAX_SAFE_INTEGER) if the 5s wall-clock timeout is hit before natural completion, avoiding frozen incomplete strokes or sparkles. Closes #1264 --- extensions/startup-banner.ts | 13 +++++++++++++ tests/startup-banner.test.ts | 12 ++++++++++++ 2 files changed, 25 insertions(+) diff --git a/extensions/startup-banner.ts b/extensions/startup-banner.ts index 54292e568..9fce0da3b 100644 --- a/extensions/startup-banner.ts +++ b/extensions/startup-banner.ts @@ -735,6 +735,9 @@ export default function (pi: ExtensionAPI) { if (finished || Date.now() - animStart > 5000) { clearInterval(state.timer!); state.timer = null; + if (!finished) { + tick = Number.MAX_SAFE_INTEGER; + } } try { tui.requestRender(); } catch { cleanup(); } }, performance ? 250 : 25); @@ -831,6 +834,16 @@ export default function (pi: ExtensionAPI) { b.addRow(); b.center(width); } + } else if (bannerConfig.showTextLogo) { + b.addRow(); + b.add("accent", "✿ "); + b.add("value", "Gentle Shell"); + b.add("accent", " ✿"); + b.center(width); + if (showRose) { + b.addRow(); + b.center(width); + } } if (showRose) { for (const roseLine of roseBase.lines) { diff --git a/tests/startup-banner.test.ts b/tests/startup-banner.test.ts index 078865c88..22cfdc884 100644 --- a/tests/startup-banner.test.ts +++ b/tests/startup-banner.test.ts @@ -202,6 +202,16 @@ test("animation modes retain banner lifetime policy, final artwork and approxima } header!.dispose(); } + + // Slow device: 5s wall-clock cap hits before animation reaches natural finish. + save("quality"); + await start!({}, ctx); boot!(); + for (let i = 0; i < 15; i++) await new Promise((resolve) => setImmediate(resolve)); + clock += 5100; + pulse!(); + assert.equal(active, false, "clears interval on 5s timeout"); + assert.equal(stripAnsi(header!.render(200).join("\n")), staticArt, "fast-forwards to complete static artwork on 5s timeout without frozen letters or sparkle"); + header!.dispose(); } finally { shutdown!(); } }); @@ -244,6 +254,8 @@ for (const showRose of [false, true]) for (const showTextLogo of [false, true]) if (width >= 160) { assert.equal(/[\u2800-\u28ff]/.test(text), showRose); assert.equal(/[▒▄▀█]/.test(text), showTextLogo); + } else if (width === 80 && showTextLogo) { + assert.match(text, /✿ Gentle Shell ✿/, "narrow terminal shows graceful fallback branding"); } assert.match(lines.join("\n"), /\x1b\[38;2;85;170;205m/, "startup labels use the saved cyan palette"); } From 4431a86f8f878d55fe5b445ced99d207512b1618 Mon Sep 17 00:00:00 2001 From: Carlos Mora Date: Sun, 20 Sep 2026 13:34:35 -0500 Subject: [PATCH 2/2] fix(startup): use compact branding in minimal mode below 122 columns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Apply the width >= logoBase.width + 2 guard to minimal mode so terminals < 122 columns render '✿ Gentle Shell ✿' instead of truncated full logo lines. - Update minimal-mode tests to verify compact branding at 80 cols and full artwork at 160 cols. --- extensions/startup-banner.ts | 24 +++++++++++++++++------- tests/startup-banner.test.ts | 6 +++++- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/extensions/startup-banner.ts b/extensions/startup-banner.ts index 9fce0da3b..8c1d442fc 100644 --- a/extensions/startup-banner.ts +++ b/extensions/startup-banner.ts @@ -787,13 +787,23 @@ export default function (pi: ExtensionAPI) { b.center(width); if (state.mode === "minimal") { - if (bannerConfig.showTextLogo) for (let logoI = 0; logoI < logoBase.lines.length; logoI++) { - const logoLine = logoBase.lines[logoI]; - b.addRow(); - b.lines[b.lines.length - 1].push( - ...buildPenLogoLine(logoLine, logoI, logoBase.lines.length, tick), - ); - b.center(width); + if (bannerConfig.showTextLogo) { + if (width >= logoBase.width + 2) { + for (let logoI = 0; logoI < logoBase.lines.length; logoI++) { + const logoLine = logoBase.lines[logoI]; + b.addRow(); + b.lines[b.lines.length - 1].push( + ...buildPenLogoLine(logoLine, logoI, logoBase.lines.length, tick), + ); + b.center(width); + } + } else { + b.addRow(); + b.add("accent", "✿ "); + b.add("value", "Gentle Shell"); + b.add("accent", " ✿"); + b.center(width); + } } } else if (horizontal) { const rowCount = Math.max(roseBase.lines.length, logoBase.lines.length); diff --git a/tests/startup-banner.test.ts b/tests/startup-banner.test.ts index 22cfdc884..dccd6ae22 100644 --- a/tests/startup-banner.test.ts +++ b/tests/startup-banner.test.ts @@ -272,7 +272,11 @@ for (const showRose of [false, true]) for (const showTextLogo of [false, true]) t.mock.timers.tick(150); const minimal = stripAnsi(header!.render(80).join("\n")); assert.doesNotMatch(minimal, /[\u2800-\u28ff]/); - assert.equal(/[▒▄▀█]/.test(minimal), showTextLogo); + assert.equal(/✿ Gentle Shell ✿/.test(minimal), showTextLogo); + if (showTextLogo) { + const wideMinimal = stripAnsi(header!.render(160).join("\n")); + assert.equal(/[▒▄▀█]/.test(wideMinimal), true); + } assert.deepEqual(writes, [], "Pi owns stdout during startup and resize"); } finally { shutdown!();