Skip to content

Expand render.Styles into a complete token set (component slots + Glyphs) #56

Description

@joestump-agent

Part of #54. This is the foundation issue — the terminal analog of the web basic catalog's --a2ui-* CSS variables.

Problem

render.Styles has six fields (CardBorder, Heading, Subheading, Caption, Button, ButtonFocused), so a host can theme those — but most chrome is hardcoded literals a host cannot touch:

  • focus cues / (render/fields.go:70-73, :104, :145-149, :196, :228)
  • button brackets "[ "/" ]" (render/button.go:38)
  • checkbox/radio marks [x]/[ ]/(•)/( ) (render/fields.go:89-91, :130-137)
  • list bullet "• " + continuation indent (render/containers.go:175-185)
  • tab separator " │ " (render/containers.go:120), modal cues / (:136-139)
  • divider glyphs / (render/styles.go:69, containers.go:87), slider cells / (fields.go:176, :186)
  • several roles share one slot: input labels, placeholders, (empty)/(unset), {binding} diagnostics, and media placeholders all reuse Caption

Proposal

Extend Styles with per-component slots and a Glyphs struct:

type Styles struct {
    // existing
    CardBorder, Heading, Subheading, Caption lipgloss.Style
    Button, ButtonFocused                    lipgloss.Style

    // new component slots
    ButtonPrimary, ButtonPrimaryFocused lipgloss.Style // see the primary-variant issue
    Label, Placeholder                  lipgloss.Style // default: alias Caption
    InputText, InputFocused             lipgloss.Style // default: unstyled
    Divider, ListBullet                 lipgloss.Style // default: unstyled
    TabActive, TabInactive              lipgloss.Style // default: Heading / unstyled
    SliderFilled, SliderEmpty           lipgloss.Style // default: unstyled

    Glyphs Glyphs
}

type Glyphs struct {
    FocusCue, FocusCueActive  string // "▏", "▎"
    ButtonOpen, ButtonClose   string // "[ ", " ]"
    CheckOn, CheckOff         string // "[x]", "[ ]"
    RadioOn, RadioOff         string // "(•)", "( )"
    ListBullet                string // "• "
    TabSeparator              string // " │ "
    ModalCue, ModalCueOpen    string // "▹", "▸"
    DividerH, DividerV        string // "─", "│"
    SliderFilled, SliderEmpty string // "█", "─"
}

Rules

  • Byte-compatible defaults: DefaultStyles() fills every slot with today's hardcoded values; all existing goldens and the no-color-SGR test (render/styles_test.go:17-37) pass unchanged.
  • Alias at construction, not render: defaults that alias an existing slot (Label = Caption) are set inside DefaultStyles(), so overriding Caption alone keeps today's behavior while a host can split the roles.
  • Zero-value glyph fallback: NewSurface backfills empty glyph strings with defaults so a partially-populated Styles from an older host doesn't render blank chrome.
  • Renderers switch from literals to s.styles.* / s.styles.Glyphs.* — no behavior change with defaults.

Out of scope

  • honoring new hints (separate issues)
  • Surface.SetStyles (separate issue)
  • createSurface.theme mapping — stays a no-op per the epic's invariants

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions