From c5d7b26d9ab9a9cfa02179bd762bc33c1df24cd9 Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Sun, 13 Sep 2026 08:05:53 -0400 Subject: [PATCH] easel: bottom-heavy, so the top of the frame is only scrollback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Slab's corner overlays all park along the top of a terminal window — the prompt rock, and now a piece preview — and the interface had its title, handle, piece, status and workspace path up there to be covered. The header even reserved sixteen columns of gutter so it could sit beside the rock, which is a concession that only makes sense while it is up there at all. It is not up there now. Scrollback fills the frame from the top, then one rule, then everything the interface says about itself, then the prompt and the footer. An overlay landing on those top rows covers lines that have already been read, which is the one thing in the frame that costs nothing to lose. The gutter is gone with it, so the header gets the full width back. Padding still falls above the transcript rather than below, so a session with four lines in it keeps them against the rule with empty space overhead — which is exactly where a preview wants to land. The title said AESTHETIC CODE, in both the boot splash and the header, two days after the rename. It says EASEL. Co-Authored-By: Claude Opus 5 (1M context) --- easel/src/render.mjs | 25 ++++++++++++++----------- easel/test/render.test.mjs | 4 ++-- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/easel/src/render.mjs b/easel/src/render.mjs index 19d74fa61f..d6cf541ea4 100644 --- a/easel/src/render.mjs +++ b/easel/src/render.mjs @@ -281,7 +281,7 @@ export function renderBoot(elapsed = 0, columns = 80, rows = 24, useColor = true const reset = useColor ? color.reset : ""; const { lines: sprite, x } = mascotAt(elapsed); - const title = "AESTHETIC CODE"; + const title = "EASEL"; // He walks along a baseline under the title, indented to the same margin the // interface uses so the two frames agree about where the left edge is. const floor = Math.floor(height / 2); @@ -383,16 +383,15 @@ export function renderFrame(state, columns = 80, rows = 24, useColor = true) { const right = `${paint(useColor, state.mode === "local" ? "status" : "highlight", mode)} · ${paint(useColor, statusTone(state.status), status)}`; const rightWidth = textWidth(`${mode} · ${status}`); - // The prompt rock parks itself over the top-right corner of the terminal - // window, so the header stops short of it. The rock is a fixed 80 points - // wide however this window is sized, which lands between eleven and sixteen - // columns across the font sizes anyone reads code in; sixteen clears it, and - // the header has the slack to give. A narrow window keeps its status and - // spends the gutter instead — nothing is worth hiding the state behind a - // rock that might not be there. - const rockGutter = width >= 64 ? 16 : 0; + // No gutter any more. Slab's corner overlays — the prompt rock, the piece + // preview — all park along the top of the window, and the header used to + // reserve sixteen columns so it could sit beside the rock. It no longer sits + // up there to be sat beside: everything the interface says about itself has + // moved to the bottom, and the top is scrollback, which is the one thing that + // can be covered without costing anything. Old lines are already read. + const rockGutter = 0; const room = Math.max(0, width - 3 - rightWidth - rockGutter); - const title = "AESTHETIC CODE"; + const title = "EASEL"; let account = state.account || "not signed in"; let piece = state.piece ? clipText(state.piece, 24) : ""; if (textWidth(`${title} ${account} ${piece}`) > room) piece = ""; @@ -494,7 +493,11 @@ export function renderFrame(state, columns = 80, rows = 24, useColor = true) { width >= 23 ? ` ${guy}${paint(useColor, "muted", clipText(helpText, width - 5))}` : paint(useColor, "muted", clipText(helpText, width)); - const lines = [header, pathLine, ...body, rule, prompt, help]; + // Bottom-heavy, so the top of the frame is nothing but scrollback. A preview + // window or a prompt rock landing over these rows covers lines that have + // already been read, rather than the title, the handle, the piece, the + // status, or the thing being typed. + const lines = [...body, rule, header, pathLine, prompt, help]; return lines .slice(0, height) .map((line) => `${ground}${fit(line, width)}${reset}`) diff --git a/easel/test/render.test.mjs b/easel/test/render.test.mjs index 8676689b6e..5735735d78 100644 --- a/easel/test/render.test.mjs +++ b/easel/test/render.test.mjs @@ -30,7 +30,7 @@ test("renders one branded interface with privacy state and prompt", () => { 20, false, ); - assert.match(frame, /AESTHETIC CODE/); + assert.match(frame, /EASEL/); assert.match(frame, /REMOTE · READY/); assert.match(frame, /YOU inspect this repository/); assert.match(frame, /AC I found the failing test/); @@ -54,7 +54,7 @@ test("shows the signed-in handle and the current piece in the header", () => { 12, false, ); - assert.match(frame, /AESTHETIC CODE @tester smiley/); + assert.match(frame, /EASEL @tester smiley/); assert.match(frame, /REMOTE · READY/); assert.match(frame, /PUB https:\/\/aesthetic\.computer\/@tester\/smiley/); assert.match(renderFrame({ workspace: "/p", mode: "remote", status: "ready", entries: [], input: "" }, 60, 12, false), /not signed in/); -- 2.51.2