Skip to main content

max / alloy

Rebuild the prompt around what each segment is for The prompt read `max@fw13 …/alloy main !↑3 130 12s`: four unrelated facts in one undifferentiated run, with nothing saying which belonged with which. `!` and `↑3` sat adjacent while being different kinds of fact, one about the working tree and one about the remote, and neither could be read without having learned it first. Grouping is now by side, which costs no punctuation. The left prompt says where you are: user, host, container, path, branch, and the state of the tree. The right says what just happened: exit code, how long it took, and when. Two subjects, two ends. Git counts read `2m 1u 3s 3a` -- count first, then a letter for what was counted, so a marker is a quantity with a unit rather than a symbol to look up. Every marker carries its own separator, since starship concatenates them and `2m1u3s` is the same run-together problem one level down. Verified against git's own porcelain in a scratch repo carrying every state at once. `st` for stashed, because staged already has `s` and the extra letter is cheaper than the collision; `diverged` needs no symbol now that ahead and behind have letters, and is just both of them. The exit code is labelled. Alone on the right with nothing around it, `130` is a number with no noun. Also out, per the rule this file now states: nerd-font icons, which draw a fact the adjacent word already carries and cost a font dependency to do it, in a terminal that has no font fallback; and the rust/python/nodejs versions, which are information rather than useful information.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-14 19:09 UTC
Signed with PGP, not checked
Commit: f4a948ab35d7739e8d701f5a55a20838ec2e8295
Parent: 33eeabd
1 file changed, +183 insertions, -108 deletions
@@ -1,129 +1,70 @@
1 1 @{! variants = default, night }
2 2 # Alloy starship configuration
3 3 #
4 - # Cross-shell prompt per docs/STACK.md#shell. Two-line prompt: line
5 - # one for context (cwd + git + language versions), line two for the
6 - # entry glyph. Color reserved for state (git dirty, non-zero exit,
7 - # insert/normal mode) per DESIGN-LANGUAGE.md.
4 + # Cross-shell prompt per docs/STACK.md#shell. Two lines: context on the first,
5 + # the entry glyph alone on the second, so a typed command always starts at the
6 + # same column no matter how long the branch name or the path is.
8 7 #
9 - # Palette drawn from docs/TOKENS.md hex values, mapped to starship's
10 - # color slots by function, not by name.
8 + # Palette drawn from docs/TOKENS.md, mapped to starship's color slots by
9 + # function rather than by name. Color is reserved for state per
10 + # DESIGN-LANGUAGE.md, which is why almost everything here is muted: the only
11 + # things that take a color are the ones saying something went wrong.
12 + #
13 + # THE RULE THIS FILE IS BUILT ON, stated 2026-08-14: every segment is a fact
14 + # somebody acts on, written as words rather than as pictures.
15 + #
16 + # What that ruled out, and why:
17 + #
18 + # - Nerd-font icons. A branch glyph in front of a branch name is the same fact
19 + # drawn twice, and it costs a font dependency to say it: shop has no font
20 + # fallback, so a glyph the font lacks draws as tofu rather than as nothing.
21 + # A prompt of words and ASCII cannot break that way.
22 + # - Language versions (rust, python, nodejs). Information, not useful
23 + # information. Inside a Rust repo it printed the toolchain version on every
24 + # line for a question nobody asks at the prompt, and `rustc -V` answers it
25 + # the moment they do.
26 + #
27 + # What it ruled in, all of them invisible until they are true:
28 + #
29 + # - The exit code as a number. A red glyph says a command failed; 1, 127, 130
30 + # and 137 are four different next actions, and the glyph cannot tell them
31 + # apart.
32 + # - The container name. `alloy pkg` puts people inside distrobox, and a box
33 + # prompt was indistinguishable from the host.
34 + # - git_state. Mid-rebase is the most important thing on screen while it lasts.
11 35
12 36 format = """
13 37 $username\
14 38 $hostname\
39 + $container\
15 40 $directory\
16 41 $git_branch\
42 + $git_state\
17 43 $git_status\
18 - $python\
19 - $rust\
20 - $nodejs\
21 - $cmd_duration\
22 44 $line_break\
23 45 $character"""
24 46
25 - right_format = """$time"""
47 + # The right prompt is a different subject from the left, and that is the whole
48 + # grouping mechanism: the left says where you are, the right says what just
49 + # happened. Exit code, duration and the time all belong to the command that
50 + # finished, so they sit together at the far end and nothing on the left has to
51 + # be separated from anything else by punctuation.
52 + right_format = """$status$cmd_duration$time"""
26 53
27 - add_newline = true
28 -
29 - # -------------------------------------------------------------------
30 - # Entry glyph — the prompt character itself
31 - # -------------------------------------------------------------------
32 - [character]
33 - success_symbol = "[▸](@{content.secondary})" # text-secondary
34 - error_symbol = "[▸](@{status.danger})" # accent-error on non-zero exit
35 - vimcmd_symbol = "[◂](@{status.success})" # accent-ok (normal mode)
36 -
37 - # -------------------------------------------------------------------
38 - # Directory — truncated, primary text
39 - # -------------------------------------------------------------------
40 - [directory]
41 - style = "bold @{content.primary}" # text-primary
42 - format = "[$path]($style)[$read_only]($read_only_style) "
43 - truncation_length = 4
44 - truncation_symbol = "…/"
45 - truncate_to_repo = true
46 - read_only = " "
47 - read_only_style = "@{status.danger}"
54 + # No blank line between prompts. The second line is already the separator, and
55 + # a blank one on top of it spends a third of a three-line prompt on nothing.
56 + add_newline = false
48 57
49 58 # -------------------------------------------------------------------
50 - # Git — branch (secondary), status (accent-warn if dirty)
51 - # -------------------------------------------------------------------
52 - [git_branch]
53 - symbol = " "
54 - style = "@{content.secondary}" # text-secondary
55 - format = "[$symbol$branch]($style) "
56 -
57 - [git_status]
58 - style = "@{status.warning}" # accent-warn
59 - format = "[$all_status$ahead_behind]($style) "
60 - conflicted = "="
61 - ahead = "↑${count}"
62 - behind = "↓${count}"
63 - diverged = "↕${ahead_count}↓${behind_count}"
64 - untracked = "?"
65 - stashed = "$"
66 - modified = "!"
67 - staged = "+"
68 - renamed = "»"
69 - deleted = "✘"
70 -
71 - # -------------------------------------------------------------------
72 - # Language versions — dim, only shown when relevant to cwd
73 - # -------------------------------------------------------------------
74 - [python]
75 - symbol = " "
76 - style = "@{content.muted}" # text-muted
77 - format = "[$symbol$version]($style) "
78 - detect_extensions = ["py"]
79 - detect_files = ["pyproject.toml", "requirements.txt", "setup.py"]
80 -
81 - [rust]
82 - symbol = " "
83 - style = "@{content.muted}"
84 - format = "[$symbol$version]($style) "
85 -
86 - [nodejs]
87 - symbol = " "
88 - style = "@{content.muted}"
89 - format = "[$symbol$version]($style) "
90 -
91 - # -------------------------------------------------------------------
92 - # Command duration — only for commands >2s
93 - # -------------------------------------------------------------------
94 - [cmd_duration]
95 - min_time = 2000
96 - style = "@{content.secondary}"
97 - format = "[$duration]($style) "
98 - show_milliseconds = false
99 -
100 - # -------------------------------------------------------------------
101 - # Right-aligned clock — a readout, styled in text-muted so it reads as
102 - # metadata. Mono throughout: the two-font working/display split was
103 - # dropped in the 2026-07-17 pivot (docs/DESIGN-LANGUAGE.md#typography),
104 - # so a terminal segment in IosevkaTerm is the rule now, not a deviation.
105 - # -------------------------------------------------------------------
106 - [time]
107 - disabled = false
108 - format = "[$time]($style)"
109 - time_format = "%H:%M"
110 - style = "@{content.muted}"
111 -
112 - # -------------------------------------------------------------------
113 - # Who and where — the two facts a prompt is for
59 + # Who and where
114 60 # -------------------------------------------------------------------
115 61 # Both were disabled here as "noise for Alloy's audience", on the reading that
116 - # a laptop user knows which machine they are sitting at. That reading did not
117 - # survive an installed test machine: fw12 and fw13 both show a bare `▸`, an
118 - # ssh session looks identical to a local one, and the first thing anyone needs
119 - # from a console on a box they just installed is which box it is.
62 + # a laptop user knows which machine they are sitting at. That did not survive
63 + # an installed test machine: two boxes gave the identical prompt and an ssh
64 + # session was indistinguishable from a local one.
120 65 #
121 - # ssh_only stays off deliberately. Showing the host only over ssh means the
122 - # prompt is silent in exactly the case where a machine is new and unfamiliar,
123 - # and it makes two sessions that behave differently look the same.
124 - #
125 - # Muted, so they read as context rather than as the line's subject. The
126 - # directory stays the only bold thing here.
66 + # ssh_only stays off deliberately. Showing the host only over ssh keeps the
67 + # prompt silent in exactly the case where a machine is new and unfamiliar.
127 68 [hostname]
128 69 disabled = false
129 70 ssh_only = false
@@ -137,19 +78,153 @@
137 78 # Not `style`. This module resolves `$style` to one of these two by who you
138 79 # are, and a plain `style` key is silently ignored: starship warns and falls
139 80 # back to its own bold yellow, which is off-palette and reads as a warning
140 - # about nothing. Caught by rendering the config and running `starship prompt`
141 - # against it, not by reading it.
81 + # about nothing.
142 82 style_user = "@{content.muted}"
143 83 # Root gets the danger color rather than the muted one. This is the one time
144 84 # the prompt should be saying something about who you are.
145 85 style_root = "@{status.danger}"
146 86
87 + # Which distrobox, blank on the host. No symbol: the name is the fact, and the
88 + # brackets are what make it read as an enclosure rather than as a word in the
89 + # path.
90 + [container]
91 + disabled = false
92 + format = "[\\[$name\\]]($style) "
93 + style = "@{status.warning}"
94 +
95 + # -------------------------------------------------------------------
96 + # Directory — the one bold thing on the line
97 + # -------------------------------------------------------------------
98 + [directory]
99 + style = "bold @{content.primary}"
100 + format = "[$path]($style)[$read_only]($read_only_style) "
101 + truncation_length = 4
102 + truncation_symbol = "…/"
103 + truncate_to_repo = true
104 + # Was a nerd-font padlock. Two letters that are always in the font say it as
105 + # well and cannot arrive as tofu.
106 + read_only = " [ro]"
107 + read_only_style = "@{status.danger}"
108 +
109 + # -------------------------------------------------------------------
110 + # Git — branch, then any operation in flight, then the working tree
111 + # -------------------------------------------------------------------
112 + [git_branch]
113 + symbol = ""
114 + style = "@{content.secondary}"
115 + format = "[$branch]($style) "
116 +
117 + # Only ever on screen mid-operation, and then it is the thing that matters:
118 + # what you are in the middle of, and how far through. The parenthesised
119 + # progress is starship's optional syntax and collapses when there is none, so
120 + # a merge reads `merge` and a rebase reads `rebase 3/7`.
121 + [git_state]
122 + style = "@{status.warning}"
123 + format = "[$state( $progress_current/$progress_total)]($style) "
124 +
125 + # Count first, then a letter for what was counted: `2m 1u 3s 3a`. Reads as a
126 + # quantity with a unit, which is what it is.
127 + #
128 + # This replaced `!?+` with arrows, where the count was missing on the working
129 + # tree markers and present on the remote ones, nothing separated one marker
130 + # from the next, and `!` and `↑3` sat adjacent while being different kinds of
131 + # fact: one is your working tree, the other is your relationship to the remote.
132 + # `!↑3` was three characters that had to be learned rather than read.
133 + #
134 + # Each marker carries its own trailing space, because starship concatenates
135 + # these with no separator and `2m1u3s` is the run-together problem again.
136 + #
137 + # `st` for stashed rather than a second `s`, which staged already has. It is
138 + # the one marker here that is not about the working tree, and the extra letter
139 + # is cheaper than the collision.
140 + [git_status]
141 + style = "@{status.warning}"
142 + format = "[$all_status$ahead_behind]($style)"
143 + conflicted = "${count}c "
144 + ahead = "${count}a "
145 + behind = "${count}b "
146 + # Diverged needs no symbol of its own now: ahead and behind already have
147 + # letters, so it is just both of them.
148 + diverged = "${ahead_count}a ${behind_count}b "
149 + untracked = "${count}u "
150 + stashed = "${count}st "
151 + modified = "${count}m "
152 + staged = "${count}s "
153 + renamed = "${count}r "
154 + deleted = "${count}d "
155 +
156 + # -------------------------------------------------------------------
157 + # The exit code, as a number
158 + # -------------------------------------------------------------------
159 + # Disabled by default in starship, and the reason to turn it on is that the
160 + # entry glyph already carries this state as a color and a color cannot count.
161 + # 127 is a typo'd command, 130 is you pressing ctrl-c, 137 is the OOM killer.
162 + # Same red glyph, three unrelated problems.
163 + #
164 + # Silent on success: a prompt that printed 0 after every command would be
165 + # saying nothing, loudly.
166 + [status]
167 + disabled = false
168 + # Labelled. On the right-hand side, away from anything that gives it context,
169 + # a bare `130` is a number with no noun. `exit 130` costs five characters and
170 + # needs nothing learned.
171 + format = "[exit $status]($style) "
172 + style = "@{status.danger}"
173 + # The number, not a name for it. `map_symbol` would turn 130 into an interrupt
174 + # glyph, which is the picture-for-a-word trade this file exists to refuse.
175 + map_symbol = false
176 + pipestatus = false
177 +
178 + # -------------------------------------------------------------------
179 + # Command duration — only for commands slow enough to have been noticed
180 + # -------------------------------------------------------------------
181 + [cmd_duration]
182 + min_time = 2000
183 + style = "@{content.secondary}"
184 + format = "[$duration]($style) "
185 + show_milliseconds = false
186 +
187 + # -------------------------------------------------------------------
188 + # Entry glyph — the prompt character itself
189 + # -------------------------------------------------------------------
190 + # Geometric shapes rather than nerd-font icons, so this survives a font with
191 + # no icon coverage. It keeps its color state even though the exit code is now
192 + # spelled out: the color is what you catch without reading.
193 + [character]
194 + success_symbol = "[▸](@{content.secondary})"
195 + error_symbol = "[▸](@{status.danger})"
196 + vimcmd_symbol = "[◂](@{status.success})"
197 +
198 + # -------------------------------------------------------------------
199 + # Right-aligned clock — when the command in front of it ran
200 + # -------------------------------------------------------------------
201 + # Kept even though the status bar is also a clock. The bar says what time it
202 + # is now; this says what time each command in the scrollback happened, which
203 + # is a different question and the only place it gets answered.
204 + [time]
205 + disabled = false
206 + format = "[$time]($style)"
207 + time_format = "%H:%M"
208 + style = "@{content.muted}"
209 +
147 210 # -------------------------------------------------------------------
148 211 # Disabled modules — noise for Alloy's audience
149 212 # -------------------------------------------------------------------
213 + # The language modules are not merely absent from `format`; they are off, so
214 + # that adding a segment later is a deliberate act rather than something that
215 + # starts rendering because a module's default changed.
150 216 [package]
151 217 disabled = true
152 218
219 + [python]
220 + disabled = true
221 +
222 + [rust]
223 + disabled = true
224 +
225 + [nodejs]
226 + disabled = true
227 +
153 228 [aws]
154 229 disabled = true
155 230