From 5be9ad8bc5fcff5b7f833920e801967364f01453 Mon Sep 17 00:00:00 2001 From: ZeroPoint95 <329227198+ZeroPoint95@users.noreply.github.com> Date: Thu, 17 Sep 2026 10:43:04 -0400 Subject: [PATCH] chat: the wordmark is drawn CodeAF, four rows, capitals on the d's cap line MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `┌─┐ / ├─┤ / └─┘` is not an `a`, it is two closed bowls of equal size stacked on each other, and two equal closed bowls are an `8`. That was the second-to-last glyph of the product's own name on the first screen of every fresh install. The owner's answer is to have the two letters at the end stand up out of the word as capitals: `A` is an apex, a crossbar and two legs; `F` is the one letter whose case is told by its arms alone, so its top arm is the longer one. A CAPITAL'S TOP IS THE ASCENDER'S TOP, and three rows of box-drawing could not give it that. A bar is drawn at the middle of its cell and a stem reaches the cell's edge, so the `d` stood half a row over any capital however it was formed — and a stem in the bottom row hung half a row under the bowls' baseline the same way. The fourth row is that half a row made whole: the x-height letters live in the lower three and leave the top row blank; the capitals and the `d`'s ascender start in the top row, the ascender as the half-stroke `╷` so its top is the capitals' bar; and every stem that ends on the baseline ends as `╵`, which stops where `└─┘` does. A `│` in the bottom row is a descender and nothing else. THE WORD IS SPELLED `codeaf` AND DRAWN `CodeAF`. `product` is unchanged, the glyph table is still keyed by its lowercase letters, and a terminal that cannot draw boxes still gets the word `codeaf`. The greeting's centring test read the unit's left edge off the wordmark's first row, which is now the cap line the x-height letters leave blank; it reads the second row. The one manual page that draws the wordmark had been drawing the retired `aforge` letterforms under the label "the codeaf wordmark" — the name law walks text and cannot see a name spelled in box-drawing — and draws the shipped forms now. Co-Authored-By: Claude Fable 5.1 --- .../1106-wordmark-a-is-not-an-eight.md | 23 +++++ internal/manual/chat/empty-screen.md | 7 +- internal/tui3/chrome_test.go | 11 ++- internal/tui3/firstrun.go | 2 +- internal/tui3/productname_test.go | 17 ++-- internal/tui3/welcome.go | 90 +++++++++++++------ 6 files changed, 109 insertions(+), 41 deletions(-) create mode 100644 docs/changes/unreleased/1106-wordmark-a-is-not-an-eight.md diff --git a/docs/changes/unreleased/1106-wordmark-a-is-not-an-eight.md b/docs/changes/unreleased/1106-wordmark-a-is-not-an-eight.md new file mode 100644 index 0000000000..2d12fd6fad --- /dev/null +++ b/docs/changes/unreleased/1106-wordmark-a-is-not-an-eight.md @@ -0,0 +1,23 @@ +--- +kind: changed +title: The wordmark is drawn `CodeAF`, four rows tall, with the capitals on the `d`'s cap line +pr: 1106 +surface: [chat] +invalidates: + - "The wordmark was three rows. It is four: the x-height letters (`c`, `o`, `e`) sit in the lower three and leave the top row blank, and the capital `A`, the capital `F` and the `d`'s ascender start in the top row. `wordmarkRows` returns four rows and `wordmarkGlyphs` is `map[rune][4]string`; anything holding three rows, or a `[3]string`, is holding the old letterforms." + - "The last two letters were lowercase forms, and the `a` was `┌─┐ / ├─┤ / └─┘`, which reads as an `8`. They are a capital `A` (`┌─┐ / │ │ / ├─┤ / ╵ ╵`) and a capital `F` with the longer top arm (`┌── / ├─ / │ / ╵ `). The whole word is ` ╷ ┌─┐ ┌──` over `┌─ ┌─┐ ┌─┤ ┌─┐ │ │ ├─ ` over `│ │ │ │ │ ├─╴ ├─┤ │ ` over `└─ └─┘ └─┘ └─┘ ╵ ╵ ╵ `." + - "A bar in box-drawing sits at the middle of its cell and only a vertical reaches the edge, so the `d`'s ascender is now the half-stroke `╷` (its top is the capitals' top bar, not the edge above it) and every stem that ends on the baseline ends as `╵` (it stops where `└─┘` does). A `│` in the bottom row is a descender and nothing else. `d` is ` ╷ / ┌─┤ / │ │ / └─┘`, with a bowl the height of the `o`'s." + - "The DRAWING is mixed case and the TEXT is not: `product` is still `codeaf`, the glyph table is still keyed by its lowercase letters, and a terminal that cannot draw boxes still gets the word `codeaf`. The lowercase spelling law over sentences, titles and `--help` is unchanged; only the letterforms under two of its letters are." + - "`internal/manual/chat/empty-screen.md` drew the retired `aforge` letterforms, with the pre-terminal `e`, under the label \"the codeaf wordmark\". It draws the shipped letterforms now. The name law walks text and cannot see a name spelled in box-drawing, so that page had spelled the old name in pictures since the rename." +--- + +Two closed bowls of equal size stacked on each other are an `8`, whatever letter +they were drawn for, and that is what the second-to-last glyph of the product's +own name had been on the first screen of every fresh install. The owner's answer +was to have the two letters at the end stand up out of the word as capitals — +and a capital's top is the ascender's top, which three rows of box-drawing could +not give it: a bar is drawn at the middle of its cell, a stem reaches the edge, +so the `d` stood half a row over the `A` and the `F` however they were formed. +The fourth row is that half a row made whole, with the x-height letters a row +lower and the half-strokes `╷` and `╵` putting the ascender's top and every +capital's feet on the same two lines the bowls use. diff --git a/internal/manual/chat/empty-screen.md b/internal/manual/chat/empty-screen.md index 6d7d26423e..6591997bdf 100644 --- a/internal/manual/chat/empty-screen.md +++ b/internal/manual/chat/empty-screen.md @@ -5,9 +5,10 @@ A new conversation opens on **one centred group** and nothing else. From the top: ``` -┌─┐ ┌─ ┌─┐ ┌─┐ ┌─┐ ┌─┐ -├─┤ ├─ │ │ │ └─┤ ├─ the codeaf wordmark -└─┘ │ └─┘ │ └─┘ └─┘ + ╷ ┌─┐ ┌── +┌─ ┌─┐ ┌─┤ ┌─┐ │ │ ├─ the codeaf wordmark, drawn CodeAF +│ │ │ │ │ ├─╴ ├─┤ │ +└─ └─┘ └─┘ └─┘ ╵ ╵ ╵ anthropic/claude-sonnet-4.5 · balanced crew a blank row › _ the message box, cursor in it diff --git a/internal/tui3/chrome_test.go b/internal/tui3/chrome_test.go index 245c359c8f..bef5bc8dd8 100644 --- a/internal/tui3/chrome_test.go +++ b/internal/tui3/chrome_test.go @@ -610,10 +610,10 @@ func TestTheEmptyScreenIsOneCentredUnit(t *testing.T) { screen := strings.Join(lines, "\n") wordmark := wordmarkRows(false) - if len(wordmark) != 3 { + if len(wordmark) != 4 { t.Fatalf("the wordmark is %d rows", len(wordmark)) } - order := []string{wordmark[0], wordmark[2], "openai/gpt-4.1-mini", glyphYou, + order := []string{wordmark[0], wordmark[3], "openai/gpt-4.1-mini", glyphYou, starterTryWord, "recent sessions", "porting the parser", "reading the registry"} last := -1 for _, want := range order { @@ -641,14 +641,17 @@ func TestTheEmptyScreenIsOneCentredUnit(t *testing.T) { } // EVERY ROW OF THE UNIT SHARES ONE LEFT EDGE, and the edge is centred at - // every width the unit is drawn at. + // every width the unit is drawn at. The edge is read off the wordmark's + // SECOND row: the first is the cap line, and the x-height letters that + // begin the word leave it blank, so its lead is the unit's edge plus the + // width of three letters that are not there yet. for _, width := range []int{60, 90, 120, 200} { a.width = width a.touch() lines := screenLines(a) unit := min(width-4, welcomeUnitWidth) want := (width - unit) / 2 - for _, row := range []string{wordmark[0], "openai/gpt-4.1-mini", glyphYou, starterSlashWord, "recent sessions"} { + for _, row := range []string{wordmark[1], "openai/gpt-4.1-mini", glyphYou, starterSlashWord, "recent sessions"} { at := unitRow(lines, row) if at < 0 { t.Fatalf("at width %d the greeting lost %q:\n%s", width, row, strings.Join(lines, "\n")) diff --git a/internal/tui3/firstrun.go b/internal/tui3/firstrun.go index c1b7575a87..5f46c85cbf 100644 --- a/internal/tui3/firstrun.go +++ b/internal/tui3/firstrun.go @@ -924,7 +924,7 @@ func setupTitle(s *setupFlow) string { // is answered before anything is asked. // // AND EVERY ONE OF THEM NAMES THE PRODUCT FROM [product] AND NEVER FROM A -// LITERAL. The wordmark three rows above this prose is drawn from that same +// LITERAL. The wordmark above this prose is drawn from that same // constant, and when the two were spelled separately the first screen anybody // ever sees said one name in the letterforms and a different one in the // sentence three rows under them. diff --git a/internal/tui3/productname_test.go b/internal/tui3/productname_test.go index 01f7198547..816c4ffa12 100644 --- a/internal/tui3/productname_test.go +++ b/internal/tui3/productname_test.go @@ -24,7 +24,7 @@ func TestTheProductIsNamedOnceAndItIsTheNameYouType(t *testing.T) { // TestTheWordmarkCanSpellTheProductsWholeName is the gate the letterforms need: // [wordmarkRows] SKIPS a letter it has no glyph for, so a missing form is not a -// build error — it is the product's name with a hole in it, three rows tall, on +// build error — it is the product's name with a hole in it, four rows tall, on // the first screen anybody sees. func TestTheWordmarkCanSpellTheProductsWholeName(t *testing.T) { for _, letter := range product { @@ -33,8 +33,8 @@ func TestTheWordmarkCanSpellTheProductsWholeName(t *testing.T) { } } rows := wordmarkRows(false) - if len(rows) != 3 { - t.Fatalf("the wordmark is %d rows, want 3", len(rows)) + if len(rows) != 4 { + t.Fatalf("the wordmark is %d rows, want 4", len(rows)) } // Three cells a letter and one between them, which is what says every letter // of the name is on the row rather than most of them. @@ -99,8 +99,8 @@ func drawnWordmark(t *testing.T) string { // strokes, which is what this asserts and nothing more. func TestTheWordmarksRightEdgeIsNeverAHoleBetweenTwoStrokes(t *testing.T) { rows := wordmarkRows(false) - if len(rows) < 3 { - t.Fatalf("the wordmark is %d rows, want at least 3", len(rows)) + if len(rows) < 4 { + t.Fatalf("the wordmark is %d rows, want at least 4", len(rows)) } edge := make([]rune, len(rows)) for i, row := range rows { @@ -117,9 +117,10 @@ func TestTheWordmarksRightEdgeIsNeverAHoleBetweenTwoStrokes(t *testing.T) { r, string(edge[r-1]), string(edge[r+1]), strings.Join(rows, "\n")) } } - // AND THE EDGE IS STILL A LETTER AND NOT A BOX. `a` closes its crossbar with - // `┤` and `e` must not, or the two letters of this name that differ in one - // cell stop differing at all. + // AND THE EDGE IS STILL A LETTER AND NOT A BOX. `e` closes its crossbar with + // the half-stroke `╴` and stands on a bowl; `a` closes its own with `┤`, + // stands on two legs and starts a row higher. Respell either into the other + // and this name has one letterform where it needs two. if wordmarkGlyphs['e'] == wordmarkGlyphs['a'] { t.Fatalf("`e` and `a` are now the same letterform %v — the name would read %q", wordmarkGlyphs['e'], strings.ReplaceAll(product, "e", "a")) } diff --git a/internal/tui3/welcome.go b/internal/tui3/welcome.go index d8d0abe67a..6380ec6c49 100644 --- a/internal/tui3/welcome.go +++ b/internal/tui3/welcome.go @@ -626,25 +626,49 @@ func (a *app) welcomePress(slot int) tea.Cmd { // ── the drawing ───────────────────────────────────────────────────────────── -// The wordmark, three rows of it, and it MUST HOLD A LETTERFORM FOR EVERY LETTER +// The wordmark, four rows of it, and it MUST HOLD A LETTERFORM FOR EVERY LETTER // OF [product] — [wordmarkRows] skips a letter it has never heard of, so a // missing glyph is not a build error, it is a word with a hole in it on the // first screen of a fresh install (TestTheWordmarkCanSpellTheProductsWholeName // is what holds the two together). // +// THE WORD IS SPELLED `codeaf` AND DRAWN `CodeAF`. The table is keyed by the +// letters of [product], which is the one spelling every sentence uses, but the +// forms under `a` and `f` are capitals: the owner wants the two letters at the +// end to stand up out of the word (2026-09-17), and a letterform table is the +// one place a drawing may differ from the text without the text changing. The +// terminal that gets the word instead of the drawing still gets `codeaf`. +// +// THE TOP ROW IS THE CAP LINE AND THE BASELINE IS ONE LINE, and both come from +// one fact about box-drawing: a bar sits at the MIDDLE of its cell and only a +// vertical reaches a cell's edge. So a letter whose top is a bar (`c`, `o`, +// `e`, the capitals) tops out half a row below a letter whose top is a stem, +// and a letter whose foot is a stem hangs half a row under one whose foot is +// a bowl. With three rows that put the `d`'s ascender half a row over the +// capitals' tops, which is the one thing a capital is not allowed to be. Now +// the x-height letters live in the lower three rows and leave the top row +// blank; the capitals and the ascender start in the top row, the ascender as +// the half-stroke `╷` so its top is the capitals' bar and not the edge above +// it; and every stem that ends on the baseline ends as the half-stroke `╵`, +// which stops where a bowl's `└─┘` does. A `│` in the bottom row is a +// DESCENDER and nothing else. +// // It is drawn from box-drawing characters rather than from a figlet font because // a figlet wordmark is nine rows of hash marks and this surface owns two: the // letterform here is the same vocabulary the rail and the rules are drawn in, // which is the whole reason it reads as part of the surface rather than as // something pasted onto it. -var wordmarkGlyphs = map[rune][3]string{ - // The bowl open on the right, on the stem `f` and `r` are drawn with. - 'c': {"┌─ ", "│ ", "└─ "}, - 'o': {"┌─┐", "│ │", "└─┘"}, - // The bowl of an `o` with the ascender on its right, which is the one - // letter of this name that rises above the others. - 'd': {" │", "┌─┤", "└─┘"}, - 'p': {"┌─┐", "├─┘", "│ "}, +var wordmarkGlyphs = map[rune][4]string{ + // The bowl open on the right, on the stem `r` is drawn with. + 'c': {" ", "┌─ ", "│ ", "└─ "}, + 'o': {" ", "┌─┐", "│ │", "└─┘"}, + // The bowl of an `o` with the ascender on its right: the one lowercase + // letter of this name that rises to the cap line, and it rises to it and + // not past it, because `╷` starts at the middle of its cell where the + // capitals' top bars are, and `│` would start at the edge half a row over + // them — which is the discrepancy the fourth row exists to remove. + 'd': {" ╷", "┌─┤", "│ │", "└─┘"}, + 'p': {" ", "┌─┐", "├─┘", "│ "}, // THE CROSSBAR ENDS IN A TERMINAL AND NOT IN A BLANK. `e` sits between two // closed letterforms, and its right column used to be `├─ `, a blank cell // with the bowl's `┐` directly above it and its `┘` directly below. A hole @@ -652,35 +676,51 @@ var wordmarkGlyphs = map[rune][3]string{ // as an open letterform; it reads as a word the terminal cut off, which is // what the wave that found this filed it as. The half-stroke closes that // cell while keeping the aperture a lowercase `e` has and `a` (`├─┤`) has - // not — the one cell that tells those two letters apart here. `c` and `f` - // need no such closing: their right column is blank on all three rows, so - // the eye reads a letter that ends rather than a stroke that is missing. - 'e': {"┌─┐", "├─╴", "└─┘"}, - 'n': {"┌─┐", "│ │", "│ │"}, - 'a': {"┌─┐", "├─┤", "└─┘"}, - 'f': {"┌─ ", "├─ ", "│ "}, - // The shoulder alone, on the stem every other ascender here is drawn with. - 'r': {"┌─┐", "│ ", "│ "}, - // The bowl of an `a` with the tail under it, which is the one letter of this - // name that hangs below the line. - 'g': {"┌─┐", "└─┤", "└─┘"}, + // not — the one cell that tells those two letters apart here. `c` needs no + // such closing: its right column is blank on all three rows, so the eye + // reads a letter that ends rather than a stroke that is missing, and `f` + // has ink in that column on its top row alone, with nothing under it to + // make a hole of the two blanks below. + 'e': {" ", "┌─┐", "├─╴", "└─┘"}, + 'n': {" ", "┌─┐", "│ │", "╵ ╵"}, + // A CAPITAL: the apex on the cap line, the crossbar a third of the way up, + // and two legs that reach the baseline apart. It used to be `┌─┐ / ├─┤ / + // └─┘`, which is not an `a` but two closed bowls of equal size stacked on + // each other, and two equal closed bowls are an `8` — which is what the + // owner read on the first screen. Opening the bottom is what makes it a + // letter, and the crossbar is what keeps it from being the `n` above. + 'a': {"┌─┐", "│ │", "├─┤", "╵ ╵"}, + // A CAPITAL, AND THE TOP ARM IS THE LONGER ONE. Both arms of the old `f` + // were two cells, which is a lowercase `f` drawn without its hook and reads + // as neither case in particular; a capital `F` is the one letter of the Latin + // alphabet whose case is told by its arms alone, the upper reaching past the + // lower, and its middle arm sits above the centre as the letter's does. That + // third cell is the only ink in the word's right-hand column, so the edge + // below it is a letter stopping and not a hole between strokes + // (TestTheWordmarksRightEdgeIsNeverAHoleBetweenTwoStrokes is the line). + 'f': {"┌──", "├─ ", "│ ", "╵ "}, + // The shoulder alone, on a stem that stops at the baseline. + 'r': {" ", "┌─┐", "│ ", "╵ "}, + // The bowl of an `o` with the tail under it, which is the one letter of this + // table that hangs below the line. + 'g': {" ", "┌─┐", "└─┤", "└─┘"}, } -// wordmarkRows is the wordmark as three unpainted rows, and the column each +// wordmarkRows is the wordmark as four unpainted rows, and the column each // letter starts on. A terminal that cannot draw the box characters gets the -// word itself — the same information, one row instead of three, and no +// word itself — the same information, one row instead of four, and no // mojibake (styles.go's [detectASCII] is the same veto the rail obeys). func wordmarkRows(ascii bool) []string { if ascii { return []string{product} } - rows := [3]string{} + rows := [4]string{} for i, letter := range product { glyph, ok := wordmarkGlyphs[letter] if !ok { continue } - for r := 0; r < 3; r++ { + for r := range rows { if i > 0 { rows[r] += " " }