lines
The configurable rows above the input bar — a status bar you compose.
interface LinesApi {
// Register a line. Throws if the name is taken.
create(name: string, definition: LineDefinition): Disposer
// Set the whole stack, top to bottom. The one place order lives.
set(entries: readonly LineEntry[]): void
// The single line above the conversation, or null to clear it.
top(name: LineName, options?: { style?: LineStyle; hidden?: boolean }): void
top(entry: LineEntry | null): void
top(): Line | null
// The default style for every line that does not declare one.
style(style: LineStyle): void
style(): LineStyle
// Move one line. Addressed by name — an index shifts when anything above it does.
move(name: LineName, position: "up" | "down" | number): void
show(name: LineName): void
hide(name: LineName): void
toggle(name: LineName): void
// Every registered line, in stack order.
list(): readonly Line[]
}
type LineContent = {
left?: LineSlot
middle?: LineSlot
right?: LineSlot
style?: LineStyle // what this line was designed for — a default, not a decree
}
type LineSlot = readonly ComponentName[] | readonly (readonly ComponentName[])[]
type LineDefinition = LineContent | readonly ComponentName[]
type LineEntry = LineName | { line: LineName; style?: LineStyle; hidden?: boolean }
type LineStyle = "plain" | "powerline" | "rounded" | "block" | "minimal"
Three layers, three owners
The split is the whole design, and it is what makes lines shareable:
| Layer | Verb | Owns | Who writes it |
|---|---|---|---|
| Component | components.create | what the data IS | an extension author |
| Line | lines.create | what sits where | shared or personal |
| Placement | lines.set | how it looks here | always you |
Style sits on the placement rather than the line, because a shared line would
otherwise ship with its look baked in — anyone installing @cody/git-line would
have to fork it to restyle. A line may still declare a default; the placement
wins when it says otherwise.
A line of your own
lines.create("me:status", {
left: ["axon:agent/name", "axon:agent/model"],
right: ["axon:session/tokens", "axon:clock"],
})
lines.set(["me:status"])
A bare array means the LEFT slot, always — never auto-distributed. Both built-in layouts are left-anchored, so it is the common case worth a shorthand, and a rule that reads the same every time beats one that guesses.
Sections
A nested array is a section — the unit a separator divides and a background fills:
lines.create("me:full", {
left: [
["axon:agent/name"], // outermost — the accent
["axon:agent/model", "axon:agent/status"], // one step in — softer
],
right: [["axon:session/tokens"], ["axon:clock"]],
})
Inside one section components share a ground, so their divider is a hairline. Between sections the ground changes, so it is a filled arrow. That distinction cannot be expressed on a flat list, which is the only reason the nesting exists.
Backgrounds step from the edge inward: the outermost section takes the accent, each step in is softer, and the innermost sits on the terminal's own ground. Position in the array is the section, so there is nothing to colour by hand — and no way for one config's bar to clash with another's.
Styles
lines.style("powerline") // the whole stack
lines.set([{ line: "me:full", style: "rounded" }]) // except this one
| Style | Looks like |
|---|---|
powerline | filled arrows between sections, hairlines within |
rounded | the same with round caps |
block | square edges — the colour change is the boundary |
minimal | a thin dot between components, no fills |
plain | spacing only |
powerline is the default. The separator block (U+E0B0–U+E0B7) predates
Nerd Fonts and ships far more widely than the icon range, so a terminal that
renders a git icon as a box will still draw these correctly. A terminal without
even that sets lines.style("plain").
The top line
lines.top("me:nav", { style: "powerline" })
lines.top(null) // clear it
One line, not a stack. The room above the conversation is scarce in a way the room below it is not — the bottom is already bounded by the input bar, and a second unbounded stack could push the conversation off screen.
Turning lines on and off
lines.toggle("axon:git")
lines.hide("axon:config")
lines.move("me:status", "up")
Or from the palette, without touching your config:
:lines toggle every registered line, each row saying which way it will go
:lines on the ones that are not showing
:lines off the ones that are
The list stays open while you flip rows — composing a bar means changing four things and looking at the result.
show() on a line that was never placed appends it to the stack, which is what
makes :lines on axon:git work on a built-in you have never mentioned.
What ships
Registered, never placed — none of these appear until you name one:
| Line | Shows |
|---|---|
axon:status | agent and model, its status and token use |
axon:git | branch, and what is uncommitted or unsynced |
axon:context | cwd, live agent count, the clock |
axon:dev | most of the catalogue at once, for working on Axon |
axon:space | a blank row, for deliberate spacing |
Next: components — the values a line shows.