Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions docs/changes/unreleased/1106-wordmark-a-is-not-an-eight.md
Original file line number Diff line number Diff line change
@@ -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.
7 changes: 4 additions & 3 deletions internal/manual/chat/empty-screen.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 7 additions & 4 deletions internal/tui3/chrome_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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"))
Expand Down
2 changes: 1 addition & 1 deletion internal/tui3/firstrun.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
17 changes: 9 additions & 8 deletions internal/tui3/productname_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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.
Expand Down Expand Up @@ -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 {
Expand All @@ -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"))
}
Expand Down
90 changes: 65 additions & 25 deletions internal/tui3/welcome.go
Original file line number Diff line number Diff line change
Expand Up @@ -626,61 +626,101 @@ 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
// punched into a block of box-drawing between two inked cells does not read
// 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] += " "
}
Expand Down
Loading